正文
大家好,我是bug菌!
今天主要跟大家分享的知识点应该不算难,不过很多朋友常常会忽略,函数指针是实现接口复用等软件设计技巧的必备知识,所以bug菌这里把函数指针的几处细节跟大家讲解一下,让大家在阅读一些代码或者是一些面试题中不至于太陌生~
1
拆解复杂函数指针
函数指针其实本质上就是一个指针类型,只是指向的是函数的入口地址,其用法与普通指针也相差不大。
然而其表现的形式较丰富,比如最经典的信号处理函数了:
1//signal
2void (*signal(int signum,void(*handler)(int))))(int);
如果这个函数的形式你还有所畏惧,可能需要多读读代码,用用函数指针了,上面是非常原汁原味的表现形式。
函数指针的形式为什么如此复杂的主要原因是 : 函数指针既可以作为函数的参数,也可以作为函数的返回值;而作为函数参数的函数指针又可以带有函数指针参数和函数指针返回值,层层嵌套,一旦展开那还是非常恐怖的。
所以我们需要想办法简化,即借助typedef,而最常用的两种定义方式如下:
1#include <stdio.h>
2#include <stdlib.h>
3
4typedef int (*tyFuc1)(int ,int );
5typedef int (tyFuc2)(int ,int );
6
7int sAdd(int a,int b)
8{
9 return(a + b);
10}
11
12/**********************************
13 * Function:main
14 * Description:函数指针的两种形式
15 * Author: bug菌
16 ********************************/
17int main(int argc, char *argv[]) {
18
19 tyFuc1 pFuc1 = sAdd;
20 tyFuc2 * pFuc2 = sAdd;
21
22 printf("sum1 = %d\n",pFuc1(1,1));
23 printf("sum2 = %d\n",pFuc2(2,2));
24
25 return 0;
26}
这两种typedef定义形式中,前者tyFuc1定义为了一个函数指针类型,而后者tyFuc2定义为一个函数类型,大家应该注意到的是在C语言中是没有函数类型的变量,只有函数名和函数指针,而函数名不会发生变化,所以基本上都会要转化为函数指针形式。
对于前面的信号处理函数,可以从外到内逐步使用typedef简化:
1void ( *signal(int signum,void(*handler)(int)) )(int)
2
3--> pFuc1 *signal(int signum,void(*handler)(int))
4
5--> pFuc1 *signal(int signum,void( *handler )(int))
6
7--> pFuc1 *signal(int signum,pFuc2 *handler )
最后把简化后的函数指针看成一个普通的指针类型,即可一眼看穿其函数表达形式,对于的函数指针处传递对应的函数名称即可。
2
之前案例分析
可能上面的例子函数指针的形式还算比较明朗,那么看一下之前一篇文章的表达形式<【C进阶】同事用void把我给秀翻了!>,当时很多朋友问bug菌这一块不是很好理解。
其中这个sCal函数主要是使用void*实现一个公共的接口,包括数据和方法,所以函数内部对fuc指针进行了一个强制类型转化,并且把param作为函数参数传入。
同样使用typedef进行简化,其真面目就很明显了:
1 ((void (*)(void*))fuc)( param );
2
3-->( (pFuc * ) fuc)( param );
3
函数指针使用细节
第一个问题 : 函数名称赋值给函数指针是否需要&符?
第二个问题 : 函数指针使用(*pFuc)(参数)还是pFuc(参数)的形式?
那么直接上一套测试代码给大家演示一下:
参考实例:
1#include <stdio.h>
2#include <stdlib.h>
3
4typedef int (*tyFuc1)(int ,int );
5typedef int (tyFuc2)(int ,int );
6
7int sAdd(int a,int b)
8{
9 return(a + b);
10}
11
12/**********************************
13 * Function:main
14 * Description:函数指针的两种形式
15 * Author: bug菌
16 ********************************/
17int main(int argc, char *argv[]) {
18
19 tyFuc1 pFuc1 = sAdd;
20 tyFuc2 * pFuc2 = &sAdd;
21
22 // sAdd 与 &sAdd 的区别
23 printf(" sAdd: = 0x%x\n",sAdd);
24 printf("&sAdd: = 0x%x\n",&sAdd);
25
26 //pFuc1与 ***pFuc1 的区别
27 printf("sum1 = %d\n",pFuc1(1,1));
28 printf("sum2 = %d\n",(*pFuc2)(2,2));
29 printf("sum3 = %d\n",(***pFuc2)(2,2));
30
31 return 0;
32}
运行结果:
对于&函数名称与函数入口地址是一样的,因为函数名称并不是一个变量,其仅表示函数的入口地址,所以&并没有不大意义。
同样对于函数指针的使用,不管如何使用*取值,其都会最终等于函数的入口地址被调用,不会再去以值为地址再去取值。
同时你也可以看如下汇编,其最终函数入口地址都是0x4016b0。
1 0x004016bd <+0>: push ebp
2 0x004016be <+1>: mov ebp,esp
3 0x004016c0 <+3>: and esp,0xfffffff0
4 0x004016c3 <+6>: sub esp,0x20
5 0x004016c6 <+9>: call 0x401d00 <__main>
6 0x004016cb <+14>: mov DWORD PTR [esp+0x1c],0x4016b0
7 0x004016d3 <+22>: mov DWORD PTR [esp+0x18],0x4016b0
8 0x004016db <+30>: mov DWORD PTR [esp+0x4],0x4016b0
9 0x004016e3 <+38>: mov DWORD PTR [esp],0x405064
10 0x004016ea <+45>: call 0x403708 <printf>
11 0x004016ef <+50>: mov DWORD PTR [esp+0x4],0x4016b0
12 0x004016f7 <+58>: mov DWORD PTR [esp],0x405074
13 0x004016fe <+65>: call 0x403708 <printf>
14 0x00401703 <+70>: mov DWORD PTR [esp+0x4],0x1
15 0x0040170b <+78>: mov DWORD PTR [esp],0x1
16 0x00401712 <+85>: mov eax,DWORD PTR [esp+0x1c]
17 0x00401716 <+89>: call eax
18 0x00401718 <+91>: mov DWORD PTR [esp+0x4],eax
19 0x0040171c <+95>: mov DWORD PTR [esp],0x405084
20 0x00401723 <+102>: call 0x403708 <printf>
21 0x00401728 <+107>: mov DWORD PTR [esp+0x4],0x2
22 0x00401730 <+115>: mov DWORD PTR [esp],0x2
23 0x00401737 <+122>: mov eax,DWORD PTR [esp+0x18]
24 0x0040173b <+126>: call eax
25 0x0040173d <+128>: mov DWORD PTR [esp+0x4],eax
26 0x00401741 <+132>: mov DWORD PTR [esp],0x40508f
27 0x00401748 <+139>: call 0x403708 <printf>
28 0x0040174d <+144>: mov DWORD PTR [esp+0x4],0x2
29 0x00401755 <+152>: mov DWORD PTR [esp],0x2
30 0x0040175c <+159>: mov eax,DWORD PTR [esp+0x18]
31 0x00401760 <+163>: call eax
32 0x00401762 <+165>: mov DWORD PTR [esp+0x4],eax
33 0x00401766 <+169>: mov DWORD PTR [esp],0x40509a
34 0x0040176d <+176>: call 0x403708 <printf>
35 0x00401772 <+181>: mov eax,0x0
36 0x00401777 <+186>: leave
37 0x00401778 <+187>: ret
enjoy~
最后
今天的内容暂时就到这里了,函数指针一些注意事项吧,觉得有所收获,记得点个赞哦~~
推荐专辑 点击蓝色字体即可跳转
☞ MCU进阶专辑
☞ “bug说”专辑
☞ 专辑|手撕C语言
☞ 专辑|经验分享