今天主要跟大家分享的知识点应该不算难,不过很多朋友常常会忽略,函数指针是实现接口复用等软件设计技巧的必备知识,所以这里把函数指针的几处细节跟大家讲解一下,让大家在阅读一些代码或者是一些面试题中不至于太陌生~

拆解复杂函数指针

函数指针其实本质上就是一个指针类型,只是指向的是函数的入口地址,其用法与普通指针也相差不大。

然而其表现的形式较丰富,比如最经典的信号处理函数了:

1//signal
2void (*signal(int signum,void(*handler)(int))))(int);

如果这个函数的形式你还有所畏惧,可能需要多读读代码,用用函数指针了,上面是非常原汁原味的表现形式。

函数指针的形式为什么如此复杂的主要原因是 : 函数指针既可以作为函数的参数,也可以作为函数的返回值;而作为函数参数的函数指针又可以带有函数指针参数和函数指针返回值,层层嵌套,一旦展开那还是非常恐怖的。

所以我们需要想办法简化,即借助,而最常用的两种定义方式如下:

 1#include 
2#include 
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 = %dn",pFuc1(1,1)); 
23    printf("sum2 = %dn",pFuc2(2,2)); 
24
25    return 0;
26}

这两种定义形式中,前者定义为了一个函数指针类型,而后者定义为一个函数类型,大家应该注意到的是在C语言中是没有函数类型的变量,只有函数名和函数指针指针函数,而函数名不会发生变化,所以基本上都会要转化为函数指针形式。

对于前面的信号处理函数,可以从外到内逐步使用简化:

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        )

最后把简化后的函数指针看成一个普通的指针类型,即可一眼看穿其函数表达形式,对于的函数指针处传递对应的函数名称即可。

之前案例分析

可能上面的例子函数指针的形式还算比较明朗,那么看一下之前一篇文章的表达形式,当时很多朋友问bug菌这一块不是很好理解。

其中这个sCal函数主要是使用void*实现一个公共的接口,包括数据和方法,所以函数内部对fuc指针进行了一个强制类型转化,并且把param作为函数参数传入。

同样使用进行简化,其真面目就很明显了:

1   ((void (*)(void*))fuc)( param );
2
3-->( (pFuc *       ) fuc)( param );

函数指针使用细节

第一个问题 : 函数名称赋值给函数指针是否需要&符?

第二个问题: 函数指针使用(*pFuc)(参数)还是pFuc(参数)的形式?

那么直接上一套测试代码给大家演示一下:

参考实例:

 1#include 
2#include 
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%xn",sAdd); 
24    printf("&sAdd: = 0x%xn",&sAdd); 
25
26    //pFuc1与 ***pFuc1 的区别 
27    printf("sum1 = %dn",pFuc1(1,1)); 
28    printf("sum2 = %dn",(*pFuc2)(2,2)); 
29    printf("sum3 = %dn",(***pFuc2)(2,2)); 
30
31    return 0;
32}

运行结果:

对于&函数名称与函数入口地址是一样的,因为函数名称并不是一个变量,其仅表示函数的入口地址指针函数,所以&并没有不大意义。

同样对于函数指针的使用,不管如何使用*取值,其都会最终等于函数的入口地址被调用,不会再去以值为地址再去取值。

同时你也可以看如下汇编,其最终函数入口地址都是。

 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 
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 
11   0x004016ef <+50>:    mov    DWORD PTR [esp+0x4],0x4016b0
12   0x004016f7 <+58>:    mov    DWORD PTR [esp],0x405074
13   0x004016fe <+65>:    call   0x403708 
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 
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 
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 
35   0x00401772 <+181>:    mov    eax,0x0
36   0x00401777 <+186>:    leave  
37   0x00401778 <+187>:    ret 

enjoy~

最后

今天的内容暂时就到这里了,函数指针一些注意事项吧,觉得有所收获,记得点个赞哦~

END


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注