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;
1
2
那么将函数指针数组的地址取出来,放到上什么类型的指针变量里呢?
这个类型是可以通过函数指针推出来的:
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函数,具体的内容在另一篇文章中:点击跳转