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