函数指针与回调函数详解

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;  //用一个函数指针接受sum函数的地址
	return 0;
}

对于一个数组,它的数组名是数组首元素的地址,对数组名取地址,会得到数组的地址
那么函数名和对函数取地址都表示这什么?
其实这两种写法没有区别,都是函数的地址,对于·1函数名去不去地址都能得到函数的地址
int (*pf)(int, int) = &sum;int (*pf)(int, int) = sum;等价


函数指针的解引用:

对函数指针解引用,再对参数列表中传参:int ret = (*pf)(1,2),这样就是对函数指针的解引用

int (*pf)(int, int) = sum;这样的写法时,可以理解为sum的地址赋给了指针pf,这时sumpf其实表示一个意思,而在平常的函数调用时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; //指向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函数,具体的内容在另一篇文章中:点击跳转


  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯癫了的狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值