目录
- 前言
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
前言
本篇文章我们将继续学习指针进阶的有关内容
函数指针
我们依然用类比的方法1来理解函数指针这一全新的概念,如图1
我们用一段代码来验证一下:
int Add(int x, int y)
{
return x+y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
打印结果如图2
进一步验证了函数指针确实是存放函数的地址。
值得注意的是,函数名和取地址函数名的结果是一样的,这有别于数组名和取地址数组名
那么如果我们想用一个指针变量来存放函数的地址该怎么书写呢?
同样是类比数组指针的写法,如下:
int (*pf)(int,int) = Add;
这里的pf就是函数指针,在书写的时候只用交代类型即可(int char float等),不需要把形参也写进去。
如果我们想通过函数指针调用这个函数怎么书写呢?
如下代码:
int Add(int x, int y)
{
return x+y;
}
int main()
{
int (*pf)(int, int) = Add;
printf("%d\n", (*pf)(3, 5));
printf("%d\n", pf(3, 5));
printf("%d\n", Add(3, 5));
return 0;
}
打印结果如图3
所以以上三种形式的书写均可实现函数的调用。
来看两段有趣的代码
先来看第一个:
(*(void (*)())0)();
对于这样复杂的代码,我们来逐步地分析:
1,将0强制类型转换为void (*)() 类型的函数指针。
2,这就意味着0地址处放着一个函数,函数没有参数,返回类型是void。
3,调用0地址处对这个函数。
我们再来看第二个:
void (*signal(int , void(*)(int)))(int);
我们同样来逐步分析:(注意这里的signal并没有和结合)
1,上述的代码是一个函数的声明。
2,函数的名字是signal。
3,函数的参数第一个是int,第二个是void( )(int)类型的函数指针。
4,该函数指针指向的函数参数是int,返回类型是void。
5,signal函数的返回类型也是一个函数指针。
6,该函数指针指向的函数参数是int,返回类型是void。
这样讲可能还是不好理解,我们再对代码进行一下简化:
typedef int* ptr_t;
typedef void(*pf_t)(int);//将void(*)(int)类型重新起个别名pf_t
int main()
{
void(* signal(int,void(*)(int)))(int);
pf_t signal(int,pf_t);
return 0;
}
函数指针数组
同样是类比数组指针,比如整型数组指针就是存放整形指针的数组,那么函数指针数组就是存放函数指针的数组
我们来看下面这段代码:
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 (*pf[4])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pf[i](8, 4);
printf("%d\n", ret);
}
return 0;
}
打印结果如图4
那么函数指针数组有什么作用呢?
我们可以通过函数指针数组来实现一个简单的计算器:
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;
int ret = 0;
int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择: >");
scanf_s("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf_s("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("结果为%d\n", ret);
}
} while (input);
return 0;
}
运行效果如图5
这样我们就通过灵活使用函数指针数组,巧妙的简化了代码,防止冗长。
指向函数指针数组的指针
指向函数指针数组的指针的书写方式如下
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf)(int, int) = Add;
int (*pfArr[4])(int, int) = { Add,Sub };
int (*(*ppfArr)[4])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针变量
return 0;
}
我们分步来理解这个式子
1,ppfArr是一个指针变量。
2,该指针变量指向的是一个数组,有四个元素。
3,该数组的每个元素类型是int ( )(int,int),是一个函数指针。
回调函数
我们先来看概念:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
那么回调函数具体怎么使用呢?看下面这段代码
int main()
{
int input = 0;
int x, y;
int ret = 0;
scanf_s("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:");
scanf_s("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
case 2://Sub
case 3://Mul
case 4://Div
}
return 0;
}
我们会发现,case等于不同的数时,总会执行重复的语句。我们能不能这样思考:假设我们把这些重复的语句封装成一个函数,然后把不同运算的函数地址转过去调用呢?
我们定义一个Calc函数:
void Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf_s("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
这样我们就实现了在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应的这样一个效果(即case等于不同的值是执行不同的响应)。
以上就是本章全部内容,下一章我们将运用回调函数的特性来模拟实现库函数–qsort(快速排序)。