C语言回调函数

文章通过3个案例展示了回调函数在C语言中的使用,包括打印任意类型数据、打印任意类型数组以及在数组中查找特定元素。回调函数通过函数指针作为参数,允许在不知道数据类型的情况下调用特定的处理函数,实现了代码的灵活性和通用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓回调函数,就是将函数指针作为参数的函数,看下面的三个案例,就知道回调函数用来干什么了。

1 打印任意类型的数据

这里只是演示回调函数,因此暂时只实现打印int、double和自定义类型的数据。假定我们要实现的函数名称为myPrint,这里需要考虑以下几个问题:
(1)如何传入数据传入数据?如果把要打印的数据当成函数的参数传入,那么参数的类型无法确定(假设函数内部是不知道要打印的数据类型),这个时候可以使用空指针,即把要打印的数据地址传入函数中myPrint;
(2)如何考虑打印?因为C语言无法像C++那样使用cout来打印,只能只用printf,即按格式打印,那么你数据传入之后,还是需要知道类型才能打印。对于这个问题,可以使用函数指针作为myPrint的参数类型,即使用回调函数,在此之前,需要为每一种类型专门写一个打印函数。

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//提供一个函数,可以打印任意类型的数据
//之所以data使用void *型,是因为可以传入任意类型的地址,要保证兼容性
void myPrint(void* data, void(*mp)(void*))		//使用函数指针作为桥梁
{
	mp(data);		
}

struct Person
{
	char name[64];
	int age;
};

void printInt(void* data)
{
	int* num = data;		//类型转换
	printf("%d\n", *num);
}

void printDouble(void* data)
{
	double* num = data;		//类型转换
	printf("%lf\n", *num);
}

void printPerson(void* data)
{
	struct Person* num = data;		//类型转换
	printf("name:%s\tage:%d\n", num->name, num->age);
}

void test01()
{
	int a = 10;
	myPrint(&a, printInt);			//你打印int型数据,需要先自己实现printfInt

	double b = 3.5;
	myPrint(&b, printDouble);		//你打印double型数据,需要先自己实现printfDouble

	struct Person p = { "aaa", 18 };
	myPrint(&p, printPerson);
}

int main() {
	
	test01();
	return EXIT_SUCCESS;
}

printInt、printDouble、printPerson三个函数的参数列表必须一致,因为它们都作为了myPrint函数的参数,既然三个函数的参数要一致,因此它们的参数只能为void *,即把要打印的数据地址传入,然后在函数内部做类型转换。

上面的程序算比较麻烦的,你都为每种数据类型单独写好了打印函数,还要myPrint干什么?但这里只是为了演示如何使用回调函数,这里是假设函数内部是不知道要打印的数据类型的。

2 打印任意类型的数组

这个比前面的稍微难一点,因为要打印的是数组,假定我们要实现的函数名称为printArray,这里需要考虑以下几个问题:
(1)假如你用空指针做为printArray的形参,那么在函数内部,数据该转换成哪种类型的指针?
(2)考虑到要遍历数据,那么知道数组首元素地址(数组名)后,还得知道步长(其实知道了元素的数据类型后,步长自然就知道了,但我们假设这个函数内部是不知道元素的数据类型的);
(3)如何打印,因为调用printf之前需要知道函数类型;

前两个问题可以一起回答,数组元素的所占字节大小通过参数传入printArray中,作为指针的步长,printArray函数内部,数据转换为char*指针,这样可以和步长配合使用(具体看下面的代码);第三个问题,为每一个类型单独写一个打印函数,然后调用printfArray时,将函数名传入。

代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//eleSize是元素占用的字节数,使用它可以控制步长
void printAllArray(void* arr, int eleSize, int len, void(*myPrint)(void*))	
{
	char* p = arr;							//之所以用char*转换,是因为char*的步长为1
	for (int i = 0; i < len; i++)
	{
		char* pAddr = p + i * eleSize;		//获取每个元素的首地址
		myPrint(pAddr);						//打印
	}
}

void printInt(void* data)
{
	int* num = data;
	printf("%d\n", *num);
}

struct Person
{
	char name[64];
	int age;
};

void printPerson(void* data)
{
	struct Person* num = data;
	printf("name:%s\tage:%d\n", num->name, num->age);
}

void test01()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	int len = sizeof(arr) / sizeof(arr[0]);
	printAllArray(arr, 4, len, printInt);
}

void test02()
{
	struct Person personArray[] = 
	{	{"aaa", 18},  
		{"bbb", 20}, 
		{"ccc", 30} };

	int len = sizeof(personArray) / sizeof(personArray[0]);
	printAllArray(personArray, sizeof(struct Person), len, printPerson);
}

int main() {
	//test01();		
	test02();

	return EXIT_SUCCESS;
}

3 查找数组中是否存在某个元素

查找的过程其实就是遍历和比较,不同类型的数组元素,比较函数的方式不一样,所以可以把这不一样的部分用函数指针来代替,这里只演示自定义数据类型的比较

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct Person
{
	char name[64];
	int age;
};

int findArrayEle(void* array, int eleSize, int len, void* data, int(*myCompare)(void*, void*))
{
	char* p = array;
	for (int i = 0; i < len; i++)
	{
		//获取到每个元素的首地址
		char* eleArray = p + eleSize * i;

		if (myCompare(eleArray, data))
		{
			return 1;		//查找成功,返回1
		}
	}
	return 0;				//查找失败,返回0
}

int comparePerson(void* data1, void* data2)
{
	struct Person* p1 = data1;
	struct Person* p2 = data2;

	if (p1->age == p2->age && strcmp(p1->name, p2->name) == 0)
		return 1;
	else
		return 0;
}

void test03()
{
	//查找数组中的元素
	struct Person personArray[] =
	{	{"aaa", 18},
		{"bbb", 20},
		{"ccc", 30} };

	//数组长度(元素个数)
	int len = sizeof(personArray) / sizeof(personArray[0]);

	//查找
	struct Person p1 = { "ccc", 30 };
	int ret = findArrayEle(personArray, sizeof(struct Person), len, &p1, comparePerson);

	//打印结果
	if (ret)
		printf("存在\n");
	else
		printf("不存在\n");

	struct Person p2 = { "ccc", 20 };
	ret = findArrayEle(personArray, sizeof(struct Person), len, &p2, comparePerson);
	//打印结果
	if (ret)
		printf("存在\n");
	else
		printf("不存在\n");	
}

int main() {
	test03();
	return EXIT_SUCCESS;
}

输出:

存在
不存在

4 总结

看完上面3个案例,基本就能理解回调函数了。其实说白了,就是把外层函数中无法用同一个函数实现的环节,用函数指针代替,外层函数根据函数指针的不同,而调用不同的具体函数。
在数据传递的时候(例如上面的传递data),回调函数和函数指针相关的具体函数(如例1中的printInt、printPerson等)最好都使用void*来传递。回调函数中用char*去接data,由于char*的步长为1,因此便于做内存操作;而定义的具体函数(如例1中的printInt、printPerson等),则是进入后首先对数据做类型转换(例1中的printInt函数,内部将data转换成int*型)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值