• 函数指针详解和简单使用


    概念

    函数指针:首先它是一个指针,一个指向函数的指针,在内存空间中存放的是函数的地址;

    引入 

    1. #include
    2. void test()
    3. {
    4. printf("hehe\n");
    5. }
    6. int main()
    7. {
    8. printf("%p\n", test);
    9. printf("%p\n", &test);
    10. //函数名是等于函数地址,所以两个打印函数结果一样
    11. return 0;
    12. }

    这个代码输出的结果是一样的,输出的是两个地址。因为,函数名是等于函数地址。

    那我们的函数的地址要想保存起来,怎么保存?直接调用下面这句:

    1. void (*pfun)(void) = test;
    2. (*pfun)(); //test(); 或者 pfun();

    pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void

    上面这个是无参例子,下面讲有参例子 :

    1. #include <stdio.h>
    2. #include <string.h>
    3. int test(const char* str)
    4. {
    5. printf("test()\n");
    6. return 0;
    7. }
    8. int main()
    9. {
    10. //函数指针 - 也是一种指针,是指向函数的指针
    11. printf("%p\n", test);
    12. printf("%p\n", &test);
    13. //函数名是等于函数地址,所以两个打印函数结果一样
    14. int (* pf)(const char*) = test;
    15. (*pf)("abc");
    16. //pftest 等价,则(*pf)("abc"); 可以写成 test("abc"); 或者 pf("abc");
    17. return 0;
    18. }
    19. //这里abc是参数,可以随便给,只要是字符型就可以。因为这里没有具体参数调用

     通过上面两个简单案例,我们可以总结函数指针的使用模板,如下:

    1. int Add(int x)
    2. {
    3. return 0;
    4. }
    5. int main()
    6. {
    7. int (*pf)(int) = &Add;//函数指针定义,返回值类型和参数类型与函数Add()相同
    8. (*pf)(int类型);
    9. }

    如果传的参数不是某个类型的数据,而是函数要怎么办?

    传入函数当作参数,则接收的形参必须是一个指针,因为传入函数的名称是指向该函数的地址,所以接收的形参应该要是函数指针。如下:

    1. int add(int x,int y)
    2. {
    3. return x + y;
    4. }
    5. void calc( int(*pf)(int , int) )
    6. {
    7. int a=3;
    8. int b=5;
    9. int ret = pf(a,b);
    10. printf("%d\n",ret);
    11. }
    12. int main()
    13. {
    14. calc(add);
    15. return 0;
    16. }

    函数指针实例

    1. //加法、减法、乘法、除法
    2. void menu()
    3. {
    4. printf("*****************************\n");
    5. printf("**** 1. add 2. sub *****\n");
    6. printf("**** 3. mul 4. div *****\n");
    7. printf("**** 0. exit *****\n");
    8. printf("*****************************\n");
    9. }
    10. int Add(int x, int y)
    11. {
    12. return x + y;
    13. }
    14. int Sub(int x, int y)
    15. {
    16. return x - y;
    17. }
    18. int Mul(int x, int y)
    19. {
    20. return x * y;
    21. }
    22. int Div(int x, int y)
    23. {
    24. return x / y;
    25. }
    26. //计算
    27. //回调函数
    28. void calc(int (*pf)(int , int))
    29. {
    30. int x = 0;
    31. int y = 0;
    32. int ret = 0;
    33. printf("请输入2个操作数:>");
    34. scanf("%d %d", &x, &y);
    35. ret = pf(x, y);
    36. printf("%d\n", ret);
    37. }
    38. int main()
    39. {
    40. int input = 0;
    41. do
    42. {
    43. menu();
    44. printf("请选择:>");
    45. scanf("%d", &input);
    46. switch (input)
    47. {
    48. case 1:
    49. calc(Add);
    50. break;
    51. case 2:
    52. calc(Sub);
    53. break;
    54. case 3:
    55. calc(Mul);
    56. break;
    57. case 4:
    58. calc(Div);
    59. break;
    60. case 0:
    61. printf("退出计算器\n");
    62. break;
    63. default:
    64. printf("选择错误\n");
    65. break;
    66. }
    67. } while (input);
    68. return 0;
    69. }

    为什么要这么复杂?看图

     

    是不是有很多重复的代码,这样子程序就不美观了吧。当然各有所好,喜欢怎么写都可以。

    其他陷阱

     注 :来源《C陷阱和缺陷》

    1. (*(void (*)())0)(); //代码1
    2. void (* signal(int,void( * )( int ) ) )(int); //代码2

    (*(void (*)())0)(); 

    在代码1里面,数字0前面的部分:void(*)()怎么理解?如果我们写成void(*p)()呢?

    可以这么理解:

    1. void:这是函数指针 p 所指向的函数的返回类型。在这里,void 表示该函数没有返回值。

    2. (*p):这部分声明了一个指针变量 p,它可以指向一个函数。* 是指针声明的一部分,表示 p 是一个指针。

    3. ():这一对括号表示指针 p 所指向的函数不接受任何参数。如果要指向接受参数的函数,括号内会包含参数列表。

     综合起来,void(*p)() 表示一个指向不接受任何参数并且没有返回值的函数的指针。你可以将这个指针指向相应类型的函数,然后通过该指针调用该函数。

    void(*)()理解:void()() 表示函数指针类型,类似于int*  char*这些类型一样

    下一步理解:( void()() )0

    1. (void()()):这是一个类型转换表达式,试图将一个整数值 0 转换为一个函数指针类型。但是,(void()()) 不是有效的类型转换,因为它没有指定要将整数值转换为哪种函数类型。如(int)3.14

    2. 0:这是一个整数字面值,表示零。

     总结:这里是一次函数调用,调用的是0作为地址处的函数。调用0地址处的这个函数。

     再理解 :* ( void()() )0

     *:这是一个解引用运算符。它用于访问指针所指向的对象或函数

    1. (void()())0 尝试将整数 0 转换为一个函数指针类型,但由于没有明确的函数指针类型,这通常会导致编译错误。

    2. * 解引用运算符试图访问一个函数指针,但由于前面的转换通常是非法的,这也会导致编译错误。

    所以代码1,可以这样理解:

    (void(*)()) 表示一个函数指针类型,该指针指向一个不接受任何参数且没有返回值(void)的函数。然后,( *(void(*)())0 ) 尝试将整数常量 0 转换为这种函数指针类型,但通常这是不合法的,因为整数不能直接转换为函数指针类型。

    接着,( *(void(*)())0 )( ) 看起来像一个函数调用表达式,它试图通过解引用一个无效的函数指针来调用一个函数。这也是不合法的,因为在这种情况下,函数指针是无效的(尝试将整数转换为函数指针是不合法的操作)。

     代码1归纳

     

     void (* signal(int,void( * )( int ) ) )(int);

    这是一个 C 语言中的函数声明,表示 `signal` 是一个函数,它接受两个参数并返回一个函数指针。让我逐步解释它的各个部分:

    1. `signal`:这是函数的名称,表示函数的标识符是 `signal`。

    2. `int`:这是函数 `signal` 的第一个参数,它是一个整数类型。

    3. `void(*)(int)`:这是函数 `signal` 的第二个参数,它是一个指向函数的指针类型。具体来说,它是一个指向接受一个整数参数并且没有返回值的函数的指针类型。

    4. `(* signal(int, void(*)(int)))`:这部分表示函数 `signal` 接受两个参数,一个整数和一个函数指针,然后返回一个函数指针。

    5. `void(*)(int)`:最后,这部分表示 `signal` 函数返回的函数指针的类型,它是一个指向接受一个整数参数并且没有返回值的函数的指针类型。

    综合起来,这个函数声明的含义是:`signal` 是一个函数,它接受一个整数参数和一个指向接受整数参数并且没有返回值的函数的指针参数,然后返回一个指向类似函数的指针,这个函数指针接受一个整数参数并且没有返回值。

    这种类型的函数通常用于设置信号处理程序,其中第一个参数是信号的编号,第二个参数是一个函数指针,指向在接收到特定信号时要执行的处理程序。函数 `signal` 返回的函数指针通常用于保存先前的信号处理程序的引用,以便以后可以还原它。

     代码2归纳

    技巧: 

    1. void (* signal(int,void( * )( int ) ) )(int);
    2. //这个函数太长,太复杂。
    3. //转换
    4. typedef void(* pf_t)(int); //把void(*)(int)类型重命名为pf_t
    5. //在调用时就可以:
    6. pf_t signal(int,pf_t);

    函数指针数组

    上面的函数指针实例中,如果还想增加更多的函数功能,就需要添加更多的条件语句。如果修改又不使得代码沉长?

    我们知道函数指针的使用如下:

    int (*p)(int, int) = add;//函数指针
    

    函数指针数组不就是把函数名变成数组形式吗?

    int (*arr[4])(int, int) = {add,sub,mul,div};//函数指针数组
    

    然后对上面的代码修改:

    1. void menu()
    2. {
    3. printf("*****************************\n");
    4. printf("**** 1. add 2. sub *****\n");
    5. printf("**** 3. mul 4. div *****\n");
    6. printf("**** 0. exit *****\n");
    7. printf("*****************************\n");
    8. }
    9. int Add(int x, int y)
    10. {
    11. return x + y;
    12. }
    13. int Sub(int x, int y)
    14. {
    15. return x - y;
    16. }
    17. int Mul(int x, int y)
    18. {
    19. return x * y;
    20. }
    21. int Div(int x, int y)
    22. {
    23. return x / y;
    24. }
    25. int main()
    26. {
    27. int input = 0;
    28. int x = 0;
    29. int y = 0;
    30. int ret = 0;
    31. //函数指针的数组
    32. //转移表
    33. int (*pfArr[])(int, int) = {0, Add, Sub, Mul, Div};
    34. /*想要增加更多函数功能只需要写好对应函数,把函数名丢到数组中就可以*/
    35. do
    36. {
    37. menu();
    38. printf("请选择:>");
    39. scanf("%d", &input);
    40. if (input == 0)
    41. {
    42. printf("退出计算器\n");
    43. }
    44. else if (input >= 1 && input <= 4)
    45. {
    46. printf("请输入2个操作数:>");
    47. scanf("%d %d", &x, &y);
    48. ret = pfArr[input](x, y);
    49. printf("%d\n", ret);
    50. }
    51. else
    52. {
    53. printf("选择错误\n");
    54. }
    55. } while (input);
    56. return 0;
    57. }

  • 相关阅读:
    java_函数式接口
    现代卷积网络实战系列6:PyTorch从零构建ResNet训练MNIST数据集
    浏览器事件循环原理 —— 任务优先级
    猫眼逆向协议抢票开发
    三个本地组策略的设置实例
    【管理运筹学】第 8 章 | 动态规划(2,动态规划的基本思想)
    pytorch中torch.mul、torch.mm、torch.matmul的区别
    软考高级之系统架构师之数据流图和流程图
    我的128创作纪念日
    Java内存模型(JMM)
  • 原文地址:https://blog.csdn.net/Good_go/article/details/133459825