一眼识破复杂函数指针的方法


正文


大家好,我是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进阶专辑 

☞  嵌入式C语言进阶专辑 

☞  “bug说”专辑 

☞ 专辑|Linux应用程序编程大全

☞ 专辑|学点网络知识

☞ 专辑|手撕C语言

☞ 专辑|手撕C++语言

☞ 专辑|经验分享

☞ 专辑|电能控制技术

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值