各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是深度剖析指针噢,在上一篇博客中,我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点,那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组的指针的知识点,现在,就让我们进入指针的世界吧


函数指针

函数指针数组

指向函数指针的数组

回调函数


函数指针

仍然是采用我们的类比法!!!

整型指针——指向整型的指针 int *

字符指针——指向字符的指针 char *

数组指针——指向数组的指针 int arr[10]; int (*p)[10]=&arr;

函数指针——指向函数的指针

数组指针中存放的是数组的地址

函数指针中存放的应该是函数的地址

那么,函数有地址吗?

#define _CRT_SECURE_NO_WARNINGS 1#includeint Add(int x, int y){return x + y;}//&Add和Add就是一样的,没有区别int main(){printf("%p\n", Add);printf("%p\n", &Add);return 0;}

输出的是两个地址,这两个地址是 test 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#define _CRT_SECURE_NO_WARNINGS 1#includeint Add(int x, int y){return x + y;}//&Add和Add就是一样的,没有区别int main(){printf("%p\n", Add);printf("%p\n", &Add);//函数的地址要存起来,就得放在函数指针变量中//pf就是函数指针int (*pf)(int, int) = Add;int ret = (*pf)(3, 5);int ret = Add(3, 5);int ret = pf(3, 5);//这三种写法都是可以的//pf前面的这颗*就是一个摆设return 0;}

下面,再来看看:

void test(){printf("hehe\n");}//下面pfun1和pfun2哪个有能力存放test函数的地址?void (*pfun1)();void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?

答案是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

源于《C陷阱和缺陷》

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

  1. 将0强制类型转化为void (*)()类型的函数指针
  2. 这就意味着0地址处放着一个函数,函数没参数,返回类型是void
  3. 调用0地址处的这个函数

其实这句代码的意思就是一次函数调用

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

signal括号里面只有类型,没有变量名,说明这是一个函数声明

void (*)(int) signal(int, void(*)(int));——可以这样理解

  1. 这句代码是一次函数声明
  2. 函数的名字是signal
  3. signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
  4. 该函数指针指向的函数参数是int,返回类型是void
  5. signal函数的返回类型也是一个函数指针
  6. 该函数指针指向的函数参数是int,返回类型是void

可以把这句代码简化一下:

typedef void(*pfun_t)(int);

pfun_t signal(int, pfun_t);//将void(*)(int)重新起个别名叫pfun_t

注意:

typedef void (*pf_t2)(int);

//pf_t2是类型名

void (*pf)(int);

//pf是函数指针变量的名字


函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int *arr[10];//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();int *parr2[10]();int (*)() parr3[10];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的使用:

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}int main(){//存放函数指针的数组——函数指针数组int (*pf[4])(int, int) = { Add,Sub,Mul,Div };//0 1 2 3int i = 0;for (i = 0; i < 4; i++){int ret = pf[i](8, 4);printf("%d\n", ret);}return 0;}

下面,我们来写一个计算器,来完成整数的+ – * /

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}void menu(){printf("#######################################\n");printf("#######1.Add2.Sub################\n");printf("#######3.Mul4.Div################\n");printf("#######0.exit################\n");printf("#######################################\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);switch (input){case 1:ret=Add(x, y);break;case 2:ret=Sub(x, y);break;case 3:ret=Mul(x, y);break;case 4:ret=Div(x, y);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}printf("%d\n", ret);} while (input);return 0;}

很轻松的,一个简易计算器的功能就实现了,我们来运行一下这个程序

但是这有问题啊!!!我选择了一个8,理应打印选择错误,而不应该打印请输入两个操作数呀!!!也不应该再打印这个ret!!!所以,我们要把程序修改一下!!!

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}void menu(){printf("#######################################\n");printf("#######1.Add2.Sub################\n");printf("#######3.Mul4.Div################\n");printf("#######0.exit################\n");printf("#######################################\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;}

这样一修改,功能实现才完全正确,但是,还是有问题,这个代码代码冗余的问题非常严重!!!如果未来要增加一些其他的功能,例如:<> & |&&|| 那么我们的代码会越写越长,case会·越写越多,这样显然是不太好的!!!

使用函数指针数组的实现:

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}void menu(){printf("#######################################\n");printf("#######1.Add2.Sub################\n");printf("#######3.Mul4.Div################\n");printf("#######0.exit################\n");printf("#######################################\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div }; //01 2 3 4do{menu();printf("请选择:>\n");scanf("%d", &input);if (input == 0){printf("退出计算器\n");break;}else if (input >= 1 && input \n");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);}else{printf("选择错误\n");}} while (input);return 0;}

函数指针数组的用途:转移表


指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int main(){int (*pf)(int, int) = Add;//函数指针数组int (*pfArr[4])(int, int) = { Add,Sub };//ppfArr是一个指向函数指针数组的指针变量int (*(*ppfArr)[4])(int, int) = &pfArr;return 0;}

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

之前我们实现了一个简易版计算器功能,但是那个代码的冗余问题非常严重,后面我们用函数指针数组的方法改造了一下这段代码,那现在,我们换一种新的方式来改造,就是我们的回调函数啦!!!

改造前:

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}void menu(){printf("#######################################\n");printf("#######1.Add2.Sub################\n");printf("#######3.Mul4.Div################\n");printf("#######0.exit################\n");printf("#######################################\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;}

改造后:

#include int Add(int a, int b){return a + b;}int Sub(int a, int b){return a - b;}int Mul(int a, int b){return a * b;}int Div(int a, int b){return a / b;}void menu(){printf("#######################################\n");printf("#######1.Add2.Sub################\n");printf("#######3.Mul4.Div################\n");printf("#######0.exit################\n");printf("#######################################\n");}void Calc(int (*pf)(int,int))//函数指针{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);break;case 4:Calc(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;}

也就是说:上述代码中的Add、Sub、Mul、Div都是回调函数!!!

之前用函数指针数组改造的那个代码实质上解决的是case语句过多的问题,而用回调函数改造的此代码实质上解决的是代码冗余的问题。

改造的两份代码解决的是完全不一样的问题,所以谈不上哪个方法更好!!!

其实回调函数还有更多内容,下一篇博客小雅兰带你玩转qsort,今天的内容就先到这里啦


好啦,告辞!!!