当前位置:   article > 正文

C知识点:指针函数【本质是一个函数,返回值是一个指针;ret *func(args, ...);】、函数指针【本质是一个指针,该指针的地址指向一个函数(指向代码段中函数入口地址的指针);用于回调函数】

指针函数

一、指针函数

指针函数: 顾名思义,它的本质是一个函数,不过它的返回值是一个指针。其声明的形式如下所示:

ret *func(args, ...);

其中,func是一个函数,args是形参列表,ret *作为一个整体,是 func函数的返回值,是一个指针的形式。

下面举一个具体的实例来做说明(文件:pointer_func.c):

  1. # include <stdio.h>
  2. # include <stdlib.h>
  3. int * func_sum(int n)
  4. {
  5. if (n < 0)
  6. {
  7. printf("error:n must be > 0\n");
  8. exit(-1);
  9. }
  10. static int sum = 0;
  11. int *p = &sum;
  12. for (int i = 0; i < n; i++)
  13. {
  14. sum += i;
  15. }
  16. return p;
  17. }
  18. int main(void)
  19. {
  20. int num = 0;
  21. printf("please input one number:");
  22. scanf("%d", &num);
  23. int *p = func_sum(num);
  24. printf("sum:%d\n", *p);
  25. return 0;
  26. }

上例就是一个指针函数的例子,其中,int * func_sum(int n)就是一个指针函数, 其功能十分简单,是根据传入的参数n,来计算从0到n的所有自然数的和,其结果通过指针的形式返回给调用方。

以上代码的运行结果如下所示:

  1. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  2. total 12
  3. drwxr-xr-x 2 root root 4096 Oct 26 22:16 ./
  4. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  5. -rw-r--r-- 1 root root 472 Oct 26 22:14 pointer_func.c
  6. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# gcc pointer_func.c -o pointer_func
  7. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  8. total 32
  9. drwxr-xr-x 2 root root 4096 Oct 26 22:16 ./
  10. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  11. -rwxr-xr-x 1 root root 16960 Oct 26 22:16 pointer_func*
  12. -rw-r--r-- 1 root root 472 Oct 26 22:14 pointer_func.c
  13. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ./pointer_func
  14. please input one number:3
  15. sum:3
  16. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ./pointer_func
  17. please input one number:4
  18. sum:6
  19. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study#

如果上述代码使用普通的局部变量来实现,也是可以的,如下所示(pointer_func2.c):

  1. # include <stdio.h>
  2. # include <stdlib.h>
  3. int func_sum2(int n)
  4. {
  5. if (n < 0)
  6. {
  7. printf("error:n must be > 0\n");
  8. exit(-1);
  9. }
  10. int sum = 0;
  11. int i = 0;
  12. for (i = 0; i < n; i++)
  13. {
  14. sum += i;
  15. }
  16. return sum;
  17. }
  18. int main(void)
  19. {
  20. int num = 0;
  21. printf("please input one number:");
  22. scanf("%d", &num);
  23. int ret = func_sum2(num);
  24. printf("sum2:%d\n", ret);
  25. return 0;
  26. }

本案例中,func_sum2函数的功能与指针函数所实现的功能完全一样。

  1. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  2. total 36
  3. drwxr-xr-x 2 root root 4096 Oct 26 22:19 ./
  4. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  5. -rw-r--r-- 1 root root 470 Oct 26 22:20 pointer_func2.c
  6. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# gcc pointer_func2.c -o pointer_func2
  7. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  8. total 56
  9. drwxr-xr-x 2 root root 4096 Oct 26 22:20 ./
  10. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  11. -rwxr-xr-x 1 root root 16928 Oct 26 22:20 pointer_func2*
  12. -rw-r--r-- 1 root root 470 Oct 26 22:20 pointer_func2.c
  13. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ./pointer_func2
  14. please input one number:4
  15. sum2:6
  16. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study#

不过在使用指针函数时,需要注意一点,相信细心地读者已经发现了,对比func_sum和func_sum2函数,除了返回值不一样之外,还有一个不同的地方在于:

  • 在func_sum中,变量sum使用的是静态局部变量,而func_sum2函数中,变量sum使用的则是普通的变量。

如果我们把指针函数的sum定义为普通的局部变量,会是什么结果呢?不妨来试验一下(pointer_func3.c):

  1. # include <stdio.h>
  2. # include <stdlib.h>
  3. int * func_sum(int n)
  4. {
  5. if (n < 0)
  6. {
  7. printf("error:n must be > 0\n");
  8. exit(-1);
  9. }
  10. int sum = 0;
  11. int *p = &sum;
  12. for (int i = 0; i < n; i++)
  13. {
  14. sum += i;
  15. }
  16. return p;
  17. }
  18. int main(void)
  19. {
  20. int num = 0;
  21. printf("please input one number:");
  22. scanf("%d", &num);
  23. int *p = func_sum(num);
  24. printf("sum:%d\n", *p);
  25. return 0;
  26. }

执行以上程序,发现仍然能得到正确的结果:
 

  1. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  2. total 60
  3. drwxr-xr-x 2 root root 4096 Oct 26 22:22 ./
  4. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  5. -rw-r--r-- 1 root root 465 Oct 26 22:23 pointer_func3.c
  6. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# gcc pointer_func3.c -o pointer_func3
  7. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  8. total 80
  9. drwxr-xr-x 2 root root 4096 Oct 26 22:23 ./
  10. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  11. -rwxr-xr-x 1 root root 16928 Oct 26 22:23 pointer_func3*
  12. -rw-r--r-- 1 root root 465 Oct 26 22:23 pointer_func3.c
  13. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ./pointer_func3
  14. please input one number:4
  15. sum:6
  16. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study#

可是如果我们把main函数里面稍微改动一下(pointer_func4.c)::

  1. # include <stdio.h>
  2. # include <stdlib.h>
  3. int * func_sum(int n)
  4. {
  5. if (n < 0)
  6. {
  7. printf("error:n must be > 0\n");
  8. exit(-1);
  9. }
  10. int sum = 0;
  11. int *p = &sum;
  12. for (int i = 0; i < n; i++)
  13. {
  14. sum += i;
  15. }
  16. return p;
  17. }
  18. int main(void)
  19. {
  20. int num = 0;
  21. printf("please input one number:");
  22. scanf("%d", &num);
  23. int *p = func_sum(num);
  24. printf("wait for a while...\n"); //此处加一句打印
  25. printf("sum:%d\n", *p);
  26. return 0;
  27. }

我们在输出sum之前打印一句话,这时看到得到的结果完全不是我们预先想象的样子,得到的并不是我们想要的答案。

  1. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  2. total 84
  3. drwxr-xr-x 2 root root 4096 Oct 26 22:25 ./
  4. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  5. -rw-r--r-- 1 root root 529 Oct 26 22:25 pointer_func4.c
  6. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# gcc pointer_func4.c -o pointer_func4
  7. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ll
  8. total 104
  9. drwxr-xr-x 2 root root 4096 Oct 26 22:26 ./
  10. drwxr-xr-x 5 root root 4096 Oct 26 21:46 ../
  11. -rwxr-xr-x 1 root root 16928 Oct 26 22:26 pointer_func4*
  12. -rw-r--r-- 1 root root 529 Oct 26 22:25 pointer_func4.c
  13. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study# ./pointer_func4
  14. please input one number:4
  15. wait for a while...
  16. sum:-2022753856
  17. root@iZm5e9phbzdxx0lysrv9t2Z:/home/C_Study#

为什么会出现上面的结果呢?
其实原因在于,一般的局部变量是存放于栈区的,当函数结束,栈区的变量就会释放掉,如果我们在函数内部定义一个变量,在使用一个指针去指向这个变量,当函数调用结束时,这个变量的空间就已经被释放,这时就算返回了该地址的指针,也不一定会得到正确的值。

上面的示例中,在返回该指针后,立即访问,的确是得到了正确的结果,但这只是十分巧合的情况,如果我们等待一会儿再去访问该地址,很有可能该地址已经被其他的变量所占用,这时候得到的就不是我们想要的结果。甚至更严重的是,如果因此访问到了不可访问的内容,很有可能造成段错误等程序崩溃的情况。

因此,在使用指针函数的时候,一定要避免出现返回局部变量指针的情况。

那么为什么用了static就可以避免这个问题呢?

原因是一旦使用了static去修饰变量,那么该变量就变成了静态变量。而静态变量是存放在数据段的,它的生命周期存在于整个程序运行期间,只要程序没有结束,该变量就会一直存在,所以该指针就能一直访问到该变量。

因此,还有一种解决方案是使用全局变量,因为全局变量也是放在数据段的,但是并不推荐使用全局变量。
 

二、函数指针

与指针函数不同,函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。

我们知道,函数的定义是存在于代码段,因此,每个函数在代码段中,也有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针

其声明形式如下所示:

ret (*p)(args, ...);

其中,ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量

1、关于函数指针的初始化

与数组类似,在数组中,数组名即代表着该数组的首地址,函数也是一样,函数名即是该数组的入口地址,因此,函数名就是该函数的函数指针。

因此,我们可以采用如下的初始化方式:

函数指针变量 =  函数名;

下面还是以一个简单的例子来具体说明一下函数指针的应用(func_pointer.c):

  1. #include <stdio.h>
  2. int max(int a, int b)
  3. {
  4. return a > b ? a : b;
  5. }
  6. int main(void)
  7. {
  8. int (*p)(int, int); //函数指针的定义
  9. //int (*p)(); //函数指针的另一种定义方式,不过不建议使用
  10. //int (*p)(int a, int b); //也可以使用这种方式定义函数指针
  11. p = max; //函数指针初始化
  12. int ret = p(10, 15); //函数指针的调用
  13. //int ret = (*max)(10,15);
  14. //int ret = (*p)(10,15);
  15. //以上两种写法与第一种写法是等价的,不过建议使用第一种方式
  16. printf("max = %d \n", ret);
  17. return 0;
  18. }

上面这个函数的功能也十分简单,就是求两个数中较大的一个数。值得注意的是通过函数指针调用的方式。

首先代码里提供了3种函数指针定义的方式,这三种方式都是正确的,比较推荐第一种、第三种定义方式。然后对函数指针进行初始化,前面已经提到过了,直接将函数名赋值给函数指针变量名即可。

上述代码运行的结果如下:

在这里插入图片描述

 调用的时候,既可以直接使用函数指针调用,也可以通过函数指针所指向的值去调用。(*p)所代表的就是函数指针所指向的值,也就是函数本身,这样调用自然不会有问题。有兴趣的同学可以去试一试。

2、为什么要使用函数指针?

那么,有不少人就觉得,本来很简单的函数调用,搞那么复杂干什么?其实在这样比较简单的代码实现中不容易看出来,当项目比较大,代码变得复杂了以后,函数指针就体现出了其优越性。

举个例子,如果我们要实现数组的排序,我们知道,常用的数组排序方法有很多种,比如快排,插入排序,冒泡排序,选择排序等,如果不管内部实现,你会发现,除了函数名不一样之外,返回值,包括函数入参都是相同的,这时候如果要调用不同的排序方法,就可以使用指针函数来实现,我们只需要修改函数指针初始化的地方,而不需要去修改每个调用的地方(特别是当调用特别频繁的时候)。
 

3、回调函数

函数指针的一个非常典型的应用就是回调函数

什么是回调函数?:回调函数就是一个通过指针函数调用的函数。其将函数指针作为一个参数,传递给另一个函数。

回调函数并不是由实现方直接调用,而是在特定的事件或条件发生时由另外一方来调用的。


同样我们来看一个回调函数的例子(callback.c):

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. //函数功能:实现累加求和
  4. int func_sum(int n)
  5. {
  6. int sum = 0;
  7. if (n < 0)
  8. {
  9. printf("n must be > 0\n");
  10. exit(-1);
  11. }
  12. for (int i = 0; i < n; i++)
  13. {
  14. sum += i;
  15. }
  16. return sum;
  17. }
  18. //这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
  19. int callback(int n, int (*p)(int))
  20. {
  21. return p(n);
  22. }
  23. int main(void)
  24. {
  25. int n = 0;
  26. printf("please input number:");
  27. scanf("%d", &n);
  28. printf("the sum from 0 to %d is %d\n", n, callback(n, func_sum)); //此处直接调用回调函数,而不是直接调用func_sum函数
  29. return 0;
  30. }

上面这个简单的demo就是一个比较典型的回调函数的例子。在这个程序中,回调函数callback无需关心func_sum是怎么实现的,只需要去调用即可。

这样的好处就是,如果以后对求和函数有优化,比如新写了个func_sum2函数的实现,我们只需要在调用回调函数的地方将函数指针指向func_sum2即可,而无需去修改callback函数内部。

以上代码的输出结果如下:
在这里插入图片描述

 回调函数广泛用于开发场景中,比如信号函数、线程函数等,都使用到了回调函数的知识。

指针函数和函数指针_chenyc4的博客-CSDN博客_函数指针和指针函数

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/573798
推荐阅读
相关标签
  

闽ICP备14008679号