目录
- 1.函数指针
- 2.函数指针数组
- 3.指向函数指针数组的指针
- 4.回调函数
1.函数指针
前面我们学的:
- 整形指针是指向整形的指针
- 字符指针是指向字符的指针
- 数组指针是指向数组的指针
所以函数指针就是指向函数的指针
假如有一个int
类型变量a
,要取它的地址就是&a
,有一个字符类型变量c
,要取它的地址就是&c
,那么一个函数的地址是怎样取到的呢。
接下来,我们取一下一个函数的地址
#include<stdio.h>
int sum(int x,int y)
{
return x + y;
}
int main()
{
printf("%p\n", &sum);
return 0;
}
取出的地址:
那么函数指针的类型怎么写呢?
以
int sum(int x,int y)
函数为例:
因为函数指针是一个指针,所以*
需要先与指针名pf
结合为(*pf)
,sum
函数有两个int
类型的参数,返回值为int
所以指向这个sum
函数的函数指针为:int (*pf)(int,int)
int sum(int x,int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = ∑ //用一个函数指针接受sum函数的地址
return 0;
}
对于一个数组,它的数组名是数组首元素的地址,对数组名取地址,会得到数组的地址
那么函数名和对函数取地址都表示这什么?
其实这两种写法没有区别,都是函数的地址,对于·1函数名去不去地址都能得到函数的地址
int (*pf)(int, int) = ∑
与int (*pf)(int, int) = sum;
等价
函数指针的解引用:
对函数指针解引用,再对参数列表中传参:int ret = (*pf)(1,2)
,这样就是对函数指针的解引用
用
int (*pf)(int, int) = sum;
这样的写法时,可以理解为sum
的地址赋给了指针pf
,这时sum
与pf
其实表示一个意思,而在平常的函数调用时int ret = sum(1.2)
,这里直接写sum
不用解引用。
所以在用函数指针的解引用时,也可以不用加*
,即为int ret = pf(1,2)
并且这里的*
其实为摆设,写不写或者写几个都是表达一个意思
接下来解读两个有意思的语句:
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
1.(*(void (*)())0)();
:
分析:(void (*)()
是一个函数指针,该函数指针指向一个无返回值,且无参数的函数,将(void (*)()
用括号括起来后面跟着0,就是将0强制类型转换成函数指针类型,最后用*
解引用强转后的函数指针,以因为参数列表中无参数,所以后面的括号中无参数。
结论:该代码是一次函数调用,首先先将代码中的0强制类型转圜为void (*)()
类型的函数指针,然后解引用调用。
2.void (*signal(int , void(*)(int)))(int);
:
分析:首先从名字signal
开始下手,可以看出signal
是一个函数,有两个参数,第一个是int
类型的,第二个是函数指针,该函数指针指向一个返回值为void
,参数为int
的函数,为了方便看,把分析过的部分取出,剩下的部分是void(* )(int)
,所以signal
函数的返回类型就是一个函数指针,该指针指向一个返回值为void
参数为int
类型的函数
这里对于
signal
函数返回值类型理解有些困难,为什么把里边函数名那部分取出来后剩下的部分就是返回类型呢?
这其实是C语言语法的锅,在不考虑语法错误的情况下,完全可以写成这样:
void(*)(int) signal(int,void(*)(int))
,这样十分容易理解返回值类型,但是这样写语法是错误的,只是易于理解而已
但是如void (*signal(int , void(*)(int)))(int);
这样写,一层层的括号让人很不容易理解,这里就可以使用typedef
简化,因为signal
的第二个参数和返回类型都是同一类,所以将void (*)(int)
简化
typedef void(*)(int) pf_t
,这样写很容易看明白就是用pf_t
名替换void (*)(int)
,但这样写是错误的,由于语法,只能写成typedef void(*pf_t)(int)
,下面的函数声明就可以写成pf_t signal(int,pf_t)
了
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
2.函数指针数组
函数指针数组——存放函数指针的数组
接下来我们写一个函数指针数组,函数指针数组顾名思义,就是有函数指针和指针数组演变而来的
int sum(int x,int y)//一个函数
{
return x + y;
}
int(*p)(int,int) = ∑ //指向sum函数的一个函数指针
int(*p)(int,int)
,里面的*
是与p
结合的,此时还是一个指针,要将它改成一个数组,就应将名字先于[]
结合,所以改为:int(*p[10])(int,int)
这里的int(*p[10])(int,int)
就是函数指针数组
函数指针有什么用呢
例如要写一个计算器程序
#include <stdio.h>
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;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:>");
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语句比较长,如果有更多的计算函数,则case语句还会变长
其实可以用函数指针数组简化
因为前面的加减乘除函数的返回值一致,参数列表一样,所以完全可以将这四个函数的地址存到一个函数指针数组里
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 (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (input>0&&input<=4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = p[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
这里最主要的代码就是int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
将四个函数的地址存到一个数组里
用户会在菜单界面后输入一个整形数字,这个整形数字是用来供用户选择功能的,同时也可以称为函数指针函数的下标,通过下标取出数组中的函数指针,进而调用相应的函数。
这样写就一定程度上使代码精短
通过这个示例,这么写函数指针数组有一种跳转的感觉,给一个下标,就能通过数组下标访问到函数的地址,再去调用函数
所以像int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
这样的叫做转移表
3.指向函数指针数组的指针
前面学习了指向数组的指针
int arr[10];
int(*pa)[10] = &arr;
那么将函数指针数组的地址取出来,放到上什么类型的指针变量里呢?
这个类型是可以通过函数指针推出来的:
int (*pf[5])(int,int);
这是一个函数指针数组,这里的名是先和[]
结合的,所以是数组,想要变成指针,就要是名先与*
结合,所以就得出:int (*(*ppf)[5])(int,int)
//ppf是指向函数指针数组的指针
int (*(*ppf)[5])(int,int) = &pf
4.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
在上面的模拟计算器程序里,由于运用case
语句,其中有大量重复的语句
所以就可以使用回调函数的方法去解决这个问题:
我们发现,在各个case
语句中,只有调用函数语句不同,其他语句都相同。所以先新建一个函数,将那些重复语句都封装在这个函数里,我们可以通过函数传参将不同的函数指针传过去
void cale(int (*p)(int,int)) //函数参数为一个函数指针
{
int x = 0,y = 0;
int ret = 0;
int input = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = p(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
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;
}
这样写就可以将case
语句中的语句做到最简,想要使用哪个功能就将哪个函数的指针传过去
通过函数指针调用的函数是回调函数,所以在这个程序里Add
,Sub
,Mul
,div
是回调函数
还有一个回调函数的应用是qsort
函数,具体的内容在另一篇文章中:点击跳转