目录

  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数

前言:

在C语言:指针详解【进阶】前篇中我们深入学习了字符指针数组指针指针数组以及数组传参和指针传参。我们对指针的应用有了较为深刻的认识,今天这里我们将更加深入的进行对更复杂的指针的探究。

函数指针

在前面我们知道一个指针变量可以指向一块内存的地址,我们也知道函数在使用时也要向内存申请一块内存空间,那我们不妨想一想我们能不能创建一个指针变量来指向一个函数呢?
我们先来看看函数的地址到底是什么?

#include void test(){printf("hehe\n");}int main(){test();printf("%p\n", &test);printf("%p\n", test);return 0;}


这里我们分别打印&testtest的值,结果两者相同,那我们可以认为&testtest都代表的是函数test的地址,这里是两者没有区别的。

当我们知道函数的地址后,我们就可以创建一个指针来指向这个函数了,但问题是函数的形式有很多种,我们的函数指针的数据类型该如何定义呢?
这里的定义其实与数组指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了
那么这里我们把函数的函数名取出来,那剩下的就是函数的数据类型,这样就可以来定义函数指针了。

函数指针在使用时可以先解引用指针pf,再在后边带上参数就行:

#include int Add(int x, int y){return x + y;}int main(){int (*pf)(int , int) = &Add;//int (*pf)(int x, int y) = &Add;//int (*pf)() = &Add;//这三种定义方法都可行int ret1 = (*pf)(3, 5);printf("%d\n", ret1);return 0;}

我们前面得知&testtest都代表的是函数test的地址,这里是两者没有区别的。那么这里我们的函数指针的使用就还有另一种方式,不用解引用pf,直接使用:

#include int Add(int x, int y){return x + y;}int main(){int (*pf)(int , int) = &Add;int ret1 = (*pf)(3, 5);printf("%d\n", ret1);int ret2 = pf(3, 5);printf("%d\n", ret2);return 0;}


其实这里的语句 int ret1 = (*pf)(3, 5);中的*就是一个摆设,没有实质性的用途,这里的*只是为了帮助我们来理解这个语句的。

所以这里我们可以任意的增加和删减*, 都是不影响结果的。
但是注意:如果你要使用*,那就必须把 *pf()括起来。

#include int Add(int x, int y){return x + y;}int main(){int (*pf)(int , int) = &Add;int ret1 = (*****pf)(3, 5);printf("%d\n", ret1);return 0;}


当我们学会函数指针后,你可能会疑惑,函数指针的使用场景是什么?我们为何不直接调用函数来使用呢?
其实函数指针本质是一个指针,我们指针的使用场景就是将数据传递到另一个函数中去使用,那当我们需要传递一个函数作为参数到另一个函数中时,就需要用到函数指针了
这里关于函数指针的使用就放在回调函数的板块讲解了。
这里我们分析两个有趣的的函数指针:

int main(){//代码1(*(void(*)())0)();//代码2void (*signal(int, void(*)(int)))(int);return 0;}

我们将代码1进行分解一下就好理解了:

代码1其实就是一次函数调用。


我们再对代码2进行分解:

代码2其实就是一次函数声明。
其实对于代码2,可以进行优化一下,使我们能更好的读懂代码;
这里我们要使用一个自定义关键词typedef(重命名)来操作。
我们将这个函数的返回值void(*)(int)进行重命名为ptr_t来进行简化代码。

//typedef void (*)(int) ptr_t; //错误写法typedef void (*ptr_t)(int);int main(){ptr_t signal(int, void(*)(int));return 0;}

注意:

在对返回值是函数指针的类型重命名时,新名字要放在函数指针的内部,不能放在后边,这样才符合语法。


函数指针数组

前面我们学习了指针数组,该数组内部可以放置相同类型的指针,我们今天又学习了函数指针,那我们是否可以创建一个函数指针数组来存放函数指针呢?答案是可以的。
函数指针数组的定义与指针数组的定义是一样的,都要有:数组元素类型,数组名,数组元素个数

void test1(){}void test2(){}void test3(){}void test4(){}int main(){void (*pf1)() = &test1;void (*pf2)() = &test2;void (*pf3)() = &test3;void (*pf4)() = &test4;void *parr[4]() = { pf1,pf2,pf3,pf4 };void (*)() parr[4] = { pf1,pf2,pf3,pf4 };void (*parr[4])() = { pf1,pf2,pf3,pf4 };//这三种函数指针数组的定义写法,哪种是正确的?return 0;}

这里和函数指针的声明类似,变量名要紧挨着*,只是这里变量名先和[]结合,作为一个数组。
所以这里的第三种定义是正确的。

void (*parr[4])() = { pf1,pf2,pf3,pf4 };

关于函数指针数组的用途:
我们知道函数指针数组中存放的是统一类型的函数指针,那么我们对于某一类数据进行相似操作时,就可以使用到函数指针数组来方便的调用一些函数了,同时有助于减少相似代码的书写,简化程序。
这里就举例实现一个简易计算器来说明函数指针数组的用途:

这里是函数的编写及头文件的声明:

#include void menu(){printf("*********************************\n");printf("******* 1.Add 2.Sub *******\n");printf("******* 3.Mul 4.Div *******\n");printf("******* 0.Exit*******\n");printf("*********************************\n");}int Add(int x, int y){return x + y;}int Sub(int x, int y){return x - y;}int Mul(int x, int y){return x * y;}int Div(int x, int y){return x / y;}

版本一(不使用函数指针数组):

int main(){int input = 0;int x = 0;int y = 0;do{menu();printf("请选择:> ");scanf("%d", &input);switch (input){case 0:{printf("退出计算器\n");break;}case 1:{printf("请输入操作数:> ");scanf("%d %d", &x, &y);printf("%d\n", Add(x, y));break;}case 2:{printf("请输入操作数:> ");scanf("%d %d", &x, &y);printf("%d\n", Sub(x, y));break;}case 3:{printf("请输入操作数:> ");scanf("%d %d", &x, &y);printf("%d\n", Mul(x, y));break;}case 4:{printf("请输入操作数:> ");scanf("%d %d", &x, &y);printf("%d\n", Div(x, y));break;}default:{printf("输入错误,请重新输入!\n");}}} while (input);return 0;}

版本二(使用函数指针数组):

int main(){int input = 0;int x = 0;int y = 0;int (*parr[5])(int, int) = { NULL, Add, Sub, Mul, Div };do{menu();printf("请选择:> ");scanf("%d", &input);if(0 < input && input < 5){printf("请输入操作数:> ");scanf("%d %d", &x, &y);printf("%d\n", parr[input](x, y));}else if (input == 0){printf("退出计算器\n");break;}else{printf("输入错误,请重新输入!\n");}} while (input);return 0;}

两者的代码一比较就可以发现,使用函数指针数组后,代码中的重复代码大大下降,代码整体简洁清晰了不少。

这里就体现了函数指针数组的用途:转移表


指向函数指针数组的指针

我们刚刚学习了函数指针数组,我们是否可以用一个指针来指向这个数组?那这个指向函数指针数组的指针又该如何定义呢?

#include void test(const char* str){printf("%s\n", str);}int main(){ //函数指针pfun void (*pfun)(const char*) = test;//&test 和 test 是一样的 //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; return 0;}

这里的定义其实与指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了
那么这里我们把函数指针数组的函数名取出来,那剩下的就是函数指针数组的数据类型,这样就可以来定义函数指针数组的指针了。

#include void test(const char* str){printf("%s\n", str);}int main(){ //函数指针pfun void (*pfun)(const char*) = test;//&test 和 test 是一样的 //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0;}

回调函数

回调函数就是一个通过函数指针调用的函数。

就是上面所说的使用函数指针进行传参的应用。
解释:

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

这里我们来进行分析一个qsort函数(库函数),进行对回调函数的了解。
这里我们需要先去了解一下qsort函数, 明白这个函数是什么用处,又该如何使用?
我们打开cplusplus网站,进行搜索。

得知qsort函数是一个排序的函数。
注意:它可以排序任意类型的数据。
函数使用:
在使用qsort函数时,我们需要只知道要排序的第一个元素的地址,要排序元素的个数,每个元素的大小,以及一个能比较两个元素的大小的函数。
这里我们唯一要设计的就是编写一个能比较两个元素的大小的函数。
注意:设计的函数要和库里给定的该比较函数模板格式要一致。

#include #include int cmp_int(const int* p1, const int* p2){return *p1 - *p2;}int main(){int arr[10] = { 7,6,1,2,8,9,3,5,0,4 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(int), cmp_int);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;}


这里我们设计的cmp_int函数就是回调函数。


讲到这里指针详解【进阶】后篇的知识讲解就结束了。
关于指针的知识是重点,也是难点,不仅仅是知识的了解,更要进行大量的练习才能巩固知识。
这几天我会出一期关于指针进阶习题的练习和讲解,来进行加深对指针的更进一步的记忆。
同时对于qsort函数,我们不仅仅只是会使用它,还要学会去自己实现一个qsort函数。同样会放在指针进阶习题的练习和讲解文章之后马上更新。


感兴趣的的小伙伴点点赞,点点关注,谢谢大家的阅读哦!!!
点点关注,后期不错过哦。
你们的鼓励就是我的动力,欢迎下次继续阅读!!!