C指针进阶(2)(函数指针与指针函数,回调函数与转移表)

本文详细介绍了C语言中的函数指针和指针函数,包括如何解析复杂的函数指针表达式,函数指针数组的使用以及回调函数的概念。通过实例展示了如何利用函数指针数组创建转移表简化代码,并给出了回调函数的简单示例,强调了回调函数在程序设计中的作用。
摘要由CSDN通过智能技术生成
在指针万字详解这篇博客中初步介绍了函数指针与指针函数,在这篇博客中,我们将学习更多的进阶知识。

在这里插入图片描述

1.回忆+练习


在《C陷阱和缺陷》这本书中,关于函数指针与指针函数这部分有两段有趣的代码

//代码1
(*(void(*)())0)();
//代码2
void (*signal(int,void(*)(int)))(int);

这段代码看似复杂,但我们如果能找到代码的特点,问题也就迎刃而解了。
代码1:
这段代码看起来毫无头绪,很混乱,要从哪里开始分析呢?从关键运算符开始分析,看到在最外层的括号里边有一个**号是在算术表达式里是乘的意思,但是这不是算术表达式,那就是解引用,什么可以解引用?指针可以解引用,那么这个*号后面一定是个指针,(void(*)())0怎么就变成了个指针呢?现在让我们回想一个知识,(int)后加一个数是强制类型转换成整型
(double)后是变成双精度浮点型,(void(*)())也是一个类型名,只不过他是函数指针类型,那么现在就应该把0按照函数指针的形式解读,函数指针中存储的是函数的入口地址,这是个什么函数呢?是一个返回值为void,没有参数的函数。你在前面加上*后面填上(),就是在调用0地址的这个无参函数。

如果对函数指针和其调用不了解的,可以去看看我的指针详解一文,有相应的版块。

代码2:
代码2看起来好像有规律的多,不像代码1一样没有入手点。

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

一看就是一个函数的声明,我们都知道函数的声明包括函数名,返回值类型,参数三部分,我们一一对应,就可以很容易解决这个问题,signal毫无疑问是个函数名,那后面的一长串就是参数,三部分我们已经找出来两部分了,那什么是返回值类型呢?我们把函数名以及参数删掉void (*)(int),一共就三部分,去掉两部分,剩下的自然就是返回值的类型,那这个返回值就是一个函数指针,函数指针指向的应该是一个参数为int,返回值为void的函数。
这里再稍微细说一下函数声明中的参数,(int,void(*)(int))第一个就是整型,没什么好说的,第二个参数则是一个函数指针,指向的函数类型与上面介绍的返回值指向的类型相同。
注意:面对此类复杂但是有迹可循的代码,我们通常使用以下步骤:
1.确定由几个部分组成
2.找出最明显的部分
3.删除它,较为清晰的观察其他部分

在这里插入图片描述
对函数指针还不熟悉的同学可以借助图来理解。对指针的学习,画图是必不可少的。

2.函数指针数组,转移表

函数指针数组就是每一个元素都是函数指针的数组。定义如下:

int(*p[5])(int ,int);

我们可以按照之前的思路分析,p先与[ ]结合形成数组,那把数组名去掉剩下的int(*)(int ,int)就是数组元素的类型了。那么函数指针数组有什么用呢?
函数指针数组通常用于转移表中,举个例子:
在学校学习或者自己应用时,我们通常会写一些小的项目来检验自己的学习成果,我们在写项目时,为了实现多种功能,经常会采用switch语句来做选项,例如:

#include<stdio.h>
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 i, x, y, ret;
	do
	{
		printf("********************************\n");
		printf("*      欢迎使用简易计算器       *\n");
		printf("*      0.exit       1.add      *\n");
		printf("*      2.sub        3.mul      *\n");
		printf("*      4.div                   *\n");
		printf("********************************\n");
		printf("请选择:>");
		scanf_s("%d", &i);
		switch (i)
		{
		case 1:
			printf("输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret=%d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret=%d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret=%d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret=%d\n", ret);
			break;
		case 0:
			printf("退出程序");
			break;
		default:
			printf("输入错误");
			break;
		}
	} while (i);
	


	return 0;
}
void PrintfFun(int i, int (*p[4])(int, int))
{
	int x, y,ret;
	if (i)
	{
		printf("请输入两个操作数:>");
		scanf_s("%d %d", &x, &y);
		ret = p[i](x, y);
		printf("ret = %d\n", ret);
	}
	else printf("欢迎您下次使用");
}

例如这个简易计算器程序,switch语句中的代码太过赘余,简化的办法有很多,在这里我们只讲转移表的方法:

#include<stdio.h>
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;
}

void PrintfFun(int i, int (*p[4])(int, int))
{

	int x, y,ret;
	if (i)
	{
		printf("请输入两个操作数:>");
		scanf_s("%d %d", &x, &y);
		ret = p[i](x, y);
		printf("ret = %d\n", ret);
	}
	else printf("欢迎您下次使用");
}
int main()
{
	int (*p1[5])(int, int) = {0,add,sub,mul,div};
	int i;
	do
	{
		printf("********************************\n");
		printf("*      欢迎使用简易计算器      *\n");
		printf("*      0.exit       1.add      *\n");
		printf("*      2.sub        3.mul      *\n");
		printf("*      4.div                   *\n");
		printf("********************************\n");
		printf("请选择:>");
		scanf_s("%d", &i);
		PrintfFun(i, p1);
	} while (i);
	
	return 0;
}

现在一看程序就简洁了很多,而且没有赘余重复,而这个转移表的关键就是应用了函数指针数组:int (*p1[5])(int, int) = {0,add,sub,mul,div};通过把函数的入口地址存入数组的函数指针中,再通过数组下标的方式就可以调用多个函数
但是需要注意的是,这几个函数的返回值类型和参数类型都应该相同,否则不能使用转移表(因为数组元素的类型都是相同的)。

3.回调函数

这里我们简单了解一下,回调函数就是通过函数指针来调用的函数,而非使用我们常见的数组名,假如你有一个函数的参数是一个函数指针,你又通过这个函数指针调用了函数指针指向的函数,那么这就叫回调函数。简单举个例子:

#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
void print(int(*p)(int,int),int x,int y)
{
	printf("ret=%d\n", (*p)(x, y));
}
int main()
{
	print(add, 5, 8);
	return 0;
}

在这个段代码中我们发现,add这个简单的相加函数并不是通过函数名的方式调用,而是在print函数中我们通过一个函数指针p来进行调用,其实回调函数并不只有这么简单,他有着十分优越的功能,感兴趣的可以百度一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值