C/C++编程笔记:C语言函数指针的理解与使用,就是这么简单明了!
1.函数指针的定义
顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。看例子:
看看上面三个表达式分别是什么意思?
C)这很容易,fun3是函数名,p1,p2是参数,其类型为char *型,函数的返回值为char *类型。
B) 也很简单,与C)表达式相比,唯一不同的就是函数的返回值类型为char**,是个二级指针。
A) fun1是函数名吗?回忆一下前面讲解数组指针时的情形。我们说数组指针这么定义或许更清晰:
int(*)[10] p;
再看看A)表达式与这里何其相似!明白了吧。这里fun1不是什么函数名,而是一个指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。同样,我们把这个表达式改写一下:
char* (*)(char* p1,char* p2) fun1;
这样子是不是好看一些呢?只可惜编译器不这么想。^_^。
2.函数指针使用的例子
上面我们定义了一个函数指针,但如何来使用它呢?先看如下例子:
我们使用指针的时候,需要通过钥匙(“*”)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。
这里需要注意到是,在Visual C++6.0里,给函数指针赋值时,可以用&fun或直接用函数名fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。这个例子很简单,就不再详细讨论了。
3.*(int*)&p —-这是什么?
也许上面的例子过于简单,我们看看下面的例子:
这是在干什么?*(int*)&p=(int)Function;表示什么意思?
别急,先看这行代码:
void(*p)();
这行代码定义了一个指针变量p,p指向一个函数,这个函数的参数和返回值都是void。
&p是求指针变量p本身的地址,这是一个32位的二进制常数(32位系统)。
(int*)&p表示将地址强制转换成指向int类型数据的指针。
(int)Function表示将函数的入口地址强制转换成int类型的数据。
分析到这里,相信你已经明白*(int*)&p=(int)Function;表示将函数的入口地址赋值给指针变量p。
那么(*p) ();就是表示对函数的调用。
讲解到这里,相信你已经明白了。其实函数指针与普通指针没什么差别,只是指向的内容不同而已。
使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。
是不是感觉上面的例子太简单,不够刺激?好,那就来点刺激的,看下面这个例子:
(*(void(*) ())0)();
这是《C Traps and Pitfalls》这本经典的书中的一个例子。没有发狂吧?下面我们就来分析分析:
第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(*) ())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。
第三步:(*(void(*) ())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。
第四步:(*(void(*) ())0)(),这是函数调用。
好像还是很简单是吧,上面的例子再改写改写:
(*(char**(*) (char**,char**))0) ( char**,char**);
如果没有上面的分析,肯怕不容易把这个表达式看明白吧。不过现在应该是很简单的一件事了。读者以为呢?
5.函数指针数组
现在我们清楚表达式
char* (*pf)(char* p);
定义的是一个函数指针pf。既然pf是一个指针,那就可以储存在一个数组里。把上式修改一下:
char* (*pf[3])(char* p);
这是定义一个函数指针数组。
它是一个数组,数组名为pf,数组内存储了3个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。
这念起来似乎有点拗口。不过不要紧,关键是你明白这是一个指针数组,是数组。函数指针数组怎么使用呢?这里也给出一个非常简单的例子,只要真正掌握了使用方法,再复杂的问题都可以应对。
如下:
6.函数指针数组的指针
看着这个标题没发狂吧?函数指针就够一般初学者折腾了,函数指针数组就更加麻烦,现在的函数指针数组指针就更难理解了。
其实,没这么复杂。前面详细讨论过数组指针的问题,这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的都是指向函数的指针。仅此而已。
下面就定义一个简单的函数指针数组指针:
char* (*(*pf)[3])(char* p);
注意,这里的pf和上一节的pf就完全是两码事了。上一节的pf并非指针,而是一个数组名;这里的pf确实是实实在在的指针。这个指针指向一个包含了3个元素的数组;这个数字里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。
这比上一节的函数指针数组更拗口。其实你不用管这么多,明白这是一个指针就ok了。其用法与前面讲的数组指针没有差别。下面列一个简单的例子:
学习C/C++编程知识,想要成为一个更加优秀的程序员,或者你学习C/C++的时候有难度,可以关注+私信小编【C/C++编程】笔者的C语言C++零基础编程学习圈,里面不仅有学习视频和文件资料,还有更多志同道合的朋友,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!
C语言基础知识:最核心的—指针!知识总结(第二部分)
指针是C语言最重要也是最难理解的部分,它在我们平时的工作中无处不在。
今天我们继续来看看指针的剩下的知识总结吧!上一批的话可以在主页看到哦~
一个指针,它指向的可以是一个结构体类型,这称为结构体指针。而一个结构体,它的成员中也可以有指针成员。
上面的代码中,定义了一个结构体变量stu1。这个变量中有一个指针变量name。还定义了一个结构体指针pstu。
我们想要通过结构体指针访问结构体成员一般有以下两种方式:
初学者常常对这两个概念搞错。首先,我认为需要理解这里说的常量是什么意思。常量就是只可读不可修改的。那常量指针和指针常量到底哪个是只可读不可修改的呢?是指针还是指针指向的内容?这里有一个方法,能让你迅速明白哪个是不可修改的。就是在声明时,以星号(*)为界,分成两部分,星号左边的和星号右边的。const在哪边,那个就是只可读不可修改的。以下面这个代码为例,我们来分析一下:以星号(*)为界,星号左边是char,没有const关键词,所以它指向的内容不是常量。然后,我们看星号的右边是const ptr,所以我们可以说ptr是一个常量。所以,这行代码声明了一个是常量的指针但是指向的内容不是常量。即这个是一个指针常量。
类似的,我们也可以分析下面的代码:
指针常量(Constant Pointers): 它的本质是一个常量,只不过这个常量是指针。由于指针是只可读不可修改的,所以这个指针不能指向别的地址了,但是该地址里的内容还是可以改变的。指针常量的声明格式如下:
我们来看下序:
上面这段程序中:
我们首先定义了两个变量var1,var2;
然后,定义了一个指针常量ptr,并且指向了var1
接着,试图让ptr指向var2
最后,打印出指针ptr指向的地址的内容
让我们来运行一下这个程序:
我们看到这个程序编译报错了:试图对只读(read-only)变量ptr进行赋值。所以,一旦我们定义了指针常量,那这个指针就不能指向其他变量了。
但是我们还是可以修改指向的地址里的内容的:
常量指针(Pointer to Constants):它的本质是一个指针,只不过它指向的值是常量(只可读,不可修改)。由于指向的是一个只可读不修改的值,所以指针不能通过它存储的地址间接修改这个地址的值,但是这个指针可以指向别的变量。
常量指针的声明格式如下:
还是有一段程序:
我们还是来分析一下这个程序:
我们定义了一个变量 var1,并且初始化为0
然后我们定义了一个指针常量ptr,并且将它指向了var1
接着,试图通过指针ptr来改变var1的值
最后,打印出ptr指向的地址的内容。
我们进行编译:
编译报错也很明显: *ptr是一个只读的。所以不能通过ptr来修改var1的值。
但是,我们可以将ptr指向其他的变量:
理解了上面两种类型的话,理解这个就很容易了。指向常量的常量指针是指这个指针既不能指向其他的地址也不能通过地址修改内容。
它的声明格式如下:
同样,下面一段程序,我想你一定知道哪里编译错误了。
编译结果:
指针与函数相结合有两种情况:指针函数、函数指针。
指针函数,它的本质是一个函数,它的返回值是一个指针。
函数名本身就是一个指针(地址),这个地址就是函数的入口地址。
输出:
0000000000401550
而函数指针,它的本质是一个指针。只不过它存的地址恰好是一个函数的地址罢了。
函数指针变量定义的格式一般是:
比如:
输出:
可以发现,两者地址相等。
函数指针类型的定义:
比如:
这样的好处就是,首先通过typedef定义一个函数指针类型PSUM,定义完后,PSUM就相当于一种新的类型,可以用此类型去定义其他函数指针变量,就不用每次都使用int(*pSum)(int, int);来定义一个函数指针变量。
输出:
说到函数指针,那还有一个概念不得不提——回调函数。因为在实际的项目代码中实在是太常见了。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
那为什么要使用回调函数呢?或者说使用回调函数有什么好处呢?回调函数允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似的事情时,可以灵活的使用不同的方法。
怎么使用回调函数:
如上述代码:可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()/Callback_2()/Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。
二维指针,或者二级指针。就是指向指针的指针。比如:
输出如下:
从输出结果也可以看到,pa存的内容*pa= 000000000000000A,刚好与a的地址相同。而ppa存的内容*ppa= 000000000061FE14也刚好等于pa的地址。它们之间的内存关系可以用如下的图表示:
处理命令行参数是指向指针的指针的一个用武之地。
一般main函数具有两个形参。第一个通常称为argc,它表示命令行参数的数目。第2个通常称为argv,它指向一组参数值。由于参数的数目并没有内在的限制,所以argv指向这组参数值(从本质上来说是一个数组)的第一个元素。这些元素的每个都是指向一个参数文本的指针。如果程序需要访问命令行参数,main函数在声明时就要加上这些参数。
举例:
在windows上执行: \\test2.exe hello world
输出:
注意,如果命令行中传递的一个参数包括空格,就需要用 \”\”将参数括起来,比如:
.\\test2.exe \”hello word\”
则上面的代码将输出:
本文关于指针的讲解就结束了。我相信你一定对指针有更深入的了解。
对啦对啦!另外的话为了帮助大家,轻松,高效学习C语言/C++,我给大家分享我收集的资源,从最零基础开始的教程到C语言项目案例,帮助大家在学习C语言的道路上披荆斩棘!可以来我粉丝群领取哦~
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!
知识分享:C语言知识干货系列——指针篇
谈到指针,我们可能会想到钟表上的指针,但这里的指针不是现实生活中看得见摸得着的钟表上的指针,c 语言中的指针只存在于逻辑思维中,物理上并不存在。
同时,指针也是C 语言中最精华的部分,通过灵活地运用指针,可以写出独具匠心、构思巧妙的程序。
c 语言中指针的实质就是地址
对于计算机中的内存,都会以字节为单位,逐一地编上号码,这个编号就是内存的地址,如下图:
图中,按字节给内存依次编上了号码,其中每个数字对应着1个字节的内存空间,而数字就是内存的地址。
C 语言中,可以用\”&\”符号来获取一个变量的内存地址。只要在变量名前加上\”&\”符号就可以获取变量名所表示的内存地址。当获取到变量的内存地址后,就相当于得到了一个指向该变量的指针。简单地总结一下:
&变量名 == 该变量的内存地址 == 指向该变量的指针
既然可以通过取地址符获取一个指向变量的指针。那怎么通过这个指针再找回原来的变量呢?
这时候就有了一个和取地址相逆的操作,我们把它称为解引用,解引用需要用到星号“ * ”,它的使用格式为:
另外,C 语言中,还允许定义专门用于存储内存地址类型的变量,我们将其称为地址变量,又被称为指针变量。
首先内存地址是内存单元的一个编号,可以把它当成一个常量看待;指针变量是一个能够存放内存地址的容器,它是一个变量;指针是无形的,我们可以把它想象成一个带箭头的长线,线尾连着指针变量,而箭头指向了指针变量所保存的内存地址处的数据。
由于指针学起来涉及的内容比较多,难度相比其他的也较大,所以下面我只是简单地谈一谈指针变量、指针与数组,指针与字符串、指针与函数、二级指针,权且当做给大家提个醒,理一理逻辑,要具体学习还得自己再编程实战中去体悟。
指针变量也是变量的一种,但它与普通变量有所不同,普通变量存储的是数据,指针变量存储的是内存地址。
定义指针变量后,不要直接去使用,因为使用赋予确定内存地址的\”定义指针\”可能会出大问题。
C 语言中,数组名所对应的值就是第一个数组元素的内存地址,即可以把数组名看为指向数组首元素的指针。通过下面的代码片段理解:
还有什么很多诸如数组指针,指针数组等等各种定义,我觉得单纯地记他们没有什么必要,本身用的就不多,就算到了用的时候,直接问问”度娘“就ok 了
字符串是常量,常量是C
语言中最简单的表达式,而所有的表达式都是有值的,那字符串常量的值是什么呢?字符串常量的值就是字符串中首字符的内存地址。例如 apple
这个字符串常量的值就是第一个字符 a 的内存地址。因此我们可以用一个char 类型的指针指向这个字符串。
函数是拥有特定功能的语句的集合,是构成程序的基本模块。在函数的定义过程中,可以将指针作为函数的参数,也可以将指针作为函数的返回值,甚至可以用指针来指向一个函数。
限于篇幅限制,这里就简单地提示一下,具体的区别或找一些具体例子来体会,大家很容易在网上搜集到的~ ~。
所谓的二级指针就是指向指针的指针。其定义格式如下:
数据类型 **变量名
指针的实质就是内存地址,由于指针变量是可以存储内存地址的变量,因此也可以将其视作指针。而指针变量本身也是变量,需要占用内存空间,因此也有内存地址。如果将这个内存地址在存储到另一个指针变量中,就形成了一个指向指针的指针,即指向指针的指针就是二级指针。
定义好二级指针后就需要对它进行初始化与赋值了
有了指针变量pi ,下面就可以定义一个二级指针并对其进行初始化了,如下:
指针是C 语言的精髓和灵魂。其中有两种相对特殊的指针,一个是空指针,另一个是void 类型的指针。
希望对你有帮助!持续分享中······
作者:博客园丨Charmchin
写在最后:对于准备学习C/C++编程的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。