记得大学时老师曾说函数的函数名是函数的入口的指针,之前看block通过clang编译生成的C代码发现很多函数指针,于是想了解函数指针与函数名有什么区别?以及函数指针一般都有些什么作用。
函数指针与函数名的区别
首先先定义一函数以及一个指向盖函数的函数指针,并分别对他们进行调用。
- #include <stdio.h>
- void fun(int x);
- int main(int argc, const charchar * argv[]) {
- void (*funP)(int);//声明函数指针funP
- funP = &fun;
- fun(1);
- (*funP)(2);
- (* fun)(3);
- funP(4);
- return 0;
- }
- //定义函数fun
- void fun(int x){
- printf("%i\n",x);
- }
- 1
- 2
- 3
- 4
这里暂时得出两个结论
1、函数名的使用基本等价于函数指针。
2、函数名也可以(* <函数名>)()来调用,只是这种方法读写都不方便,所以被简化了。
得到一个问题是:为什么使用“funP = &fun”的形式对funP赋值,而不直接使用“funP = fun”
对于上面得出的问题,我们试着直接输出funP与fun作为指针的值,进行比较。代码如下
- <span style="font-size:14px;">#include <stdio.h>
- void fun(int x);
- int main(int argc, const charchar * argv[]) {
- void (*funP)(int);
- funP = &fun;
- printf("%p\n",&fun);
- printf("%p\n\n",funP);
- printf("%p\n",fun);
- printf("%p\n\n",&funP);
- return 0;
- }
- //函数定义
- void fun(int x){
- printf("%i\n",x);
- }</span>
- 0x100000f40
- 0x100000f40
- 0x100000f40
- 0x7fff5fbff888
- Program ended with exit code: 0
其次在打印fun作为指针的内容时,我们发现fun(暂时看做是一个指针)的内容就是它的地址,fun是一个指向自己的指针。根据大家常说的fun作为函数的入口的依据,那么它的地址就是函数入口的地址,其次我们通过fun来找到函数,那么他就应该指向函数的入口。这么解释似乎能说的通为什么fun是一个指向自己的指针。虽然感觉有点怪怪的。
经过上面两端代码后我们好像能确定函数名就是函数指针,那我们看看作为函数指针,它能否做其他函数指针能做的事,比如赋值。
- funP = &fun;
- (*funP)(1);
- funP = fun;
- (*funP)(2);
其次对fun进行赋值。发现下面两条都无法通过编译。
- fun = funP;
- fun = &funp;
由此我们发现函数名作为指针无法被赋值。对此,在网上发现两种解释
第一种:函数名与FunP函数指针都是函数指针。fun是一个函数指针常量,funP是一个函数数指针变量。
虽然通过常量与变量来解释函数名无法赋值可以帮助理解,但是我们发现对fun赋值时编译器给的错误提示并不是说对常量进行赋值,而是告诉我们=号两端格式不匹配。对此,第二种理解更合理。
第二种:函数名和数组名实际上都不是指针,但是,在使用时可以退化成指针,即编译器可以帮助我们实现自动的转换。
这也可以解释为什么当我们在=号右侧使用函数名时,无论是取值还是取地址都没有问题,因为编译替我们做了相当于强制类型转换的工作,而在当函数名在=号左侧时,右侧的函数指针并没有这个功能,毕竟他们俩不是同一种结构。
函数指针的作用
当我无法区别函数名与函数指针时,我很好奇既然函数名也是函数指针类型,那为什么不直接使用函数名。提出函数指针的目的是什么,它有什么作用。虽然现在明白了函数名不等于函数指针,但是问题还是要解决。1、作为变量传递,可称为参数
既然函数指针如同别的指针变量一样通过*来获得,那么函数指针作为变量,自然可以进行赋值,取值操作,可以作为函数的参数进行传递。普通指针变量能做什么它就能做什么。
2、优化函数调用,封装
通常函数名的命名都是见名知意,直接用函数名调用可读性自然要好,但如果是不想给别人查看的代码,被人破解后通过函数名直接就了解具体函数作用与函数调用,而函数指针可以作为函数的一层外衣,提供一定的保护作用。
其次通过函数指针来调用函数,可以起到一定的封装效果,函数指针作为引用层(中层),函数作为实现层(底层),便于分层设计,函数指针可以为上层用户提供统一借口,便于系统抽象各个功能或操作,降低程序耦合度。如
fopen就是个例子,他可以打开文件。而C里面将磁盘文件、串口、USB等诸多设备抽象为文件。为C++实现多态性的虚函数表也是通过函数指针实现。
3、回调函数
这是最重要的使用场景了,回调函数就是一个通过函数指针调用的函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。举个简单的例子。如去干洗店洗衣服,我们通常会留下电话号码,而干洗店洗好衣服后就会通过电话通知我们,让我们来取衣服。这个场景里电话号码就是回调函数,相当于函数指针。这在实现通知机制的时候就能看到。其次在我们编写一个对一般数据类型进行操作的库时,为了能让库可用于多种数据类型(int、float、string),也可以使用函数指针,并进行回调。