【C】函数指针数组 介绍 + 使用(转移表)、函数指针 介绍 + 使用(回调函数)、补充 qsort 对任意类型能够比较的原理(void* + char*)


👉🔗 点击跳转:【C++11】std::function 包装器(又叫适配器),std::bind 绑定


概念

函数指针
首先是一个 指针,指针指向一个函数。

函数指针 写法:函数返回值(*pf)(函数参数列表)

变量类型:函数返回值(*)(函数参数列表)

函数指针数组
首先是一个 数组,数组里面存放的元素是一个个指针,指针指向的对象是 参数相同,返回类型相同 的函数。

函数指针数组 写法:函数返回值(*pfAr[n])(函数参数列表)

变量类型:函数返回值(*)(函数参数列表)[n]

指向函数指针数组的指针 (了解) :
首先是一个 指针,指针指向的是一个函数指针数组。

指向函数指针数组的指针 写法:函数返回值(*(*pp)[n])(函数参数列表)

变量类型:函数返回值(*)(函数参数列表)[n]*

/*int Add(int x, int y)
{
	return x + y;
}*/

// 1.【函数指针】
int (*pf)(int, int) = Add; 


// 2.【函数指针数组】
int (*pfAr[4])(int, int) = {Add, Sub, mul, div};
// pfAr 的类型为去掉变量名 int(*)(int,int)[4]


// 3.【指向'函数指针数组'的指针】
int (*(*pp)[4])(int, int) = &pfAr;
//pp 是“指向'函数指针数组'的指针”
//pp 的类型是 int(*)(int,int)[4]*
//pp 指向的数组存放的元素类型是 int(*)(int,int)

函数指针数组,可以用于存放 参数相同、返回类型相同的 一系列函数地址。

即使如此,什么场景下能使用?我们又该如何使用呢?

函数指针数组 的主要用途:【转移表】

当我们把返回值和参数相同、功能相似的函数地址放在一个数组里时,这就叫一份 “转移表”。

面对下面实现,原先我们只能使用 switch 调用一个个代码。可以看见的是,switch 逻辑写出的毫无封装可言的代码,甚至经不起位置的调整,面对复杂代码工作量巨大。

如果使用转移表,则避免了程序设计的冗杂,便于后续增添功能和维护。


🐎代码实现一个只有简单加减乘除的计算机:
函数实现:

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 menu()
{
	printf("*************************************\n");
	printf("*******   1.add      2.sub   ********\n");
	printf("*******   3.mul      4.div   ********\n");
	printf("*******   0.exit             ********\n");
	printf("*************************************\n");
}

主体功能实现:

// 主程序
int main()
{
	int input = 0;
	int ret = 0;
	int x = 0;
	int y = 0;

	// 转移表
	int(* pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };

	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);

		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);

			ret = pfArr[input](x, y);//[下标](参数)
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器");
			break;
		}
		else
		{
			printf("选择错误");
			break;
		}
	} while (input);

	return 0;
}

🐎接着化简:
用一个封装的函数来调用别的函数 -->回调函数

void calc(int (*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);
}

// 主程序
int main()
{
	int input = 0;
	int ret = 0;
	int x = 0;
	int y = 0;

	// 转移表
	int(* pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };

	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; 
		defualt:
			printf("选择错误\n");
			break;
		}

	} while (input);

	return 0;
}

函数指针的主要用途:【回调函数】

回调函数:用一个封装的函数来调用别的函数

面对传统冒泡排序只能对整型排序,我们做如下思考:
2 个整形,用关系运算符比较大小,
2 个字符串,用strcmp比较大小,
2 个结构体,也得指定比较方式…

理想状态:
cmp_int();
cmp_char();
cmp_struct();…
同的类型传入都可以比较,库里面其实是有这个函数的。

前提补充1:qsort

qsort - C语言标准库提供的排序函数 - 可以排序任意类型的数据

void qsort(void *base,
			size_t num,		
			size_t size,		
			int (*compar)(const void *, const void*));

void base : 待排序数据的起始地址(void 可以接受任何类型的指针)
size_t num :待排序数据的元素个数
size_t size : 待排序数据的元素大小(单位是字节)
int (*compar)(const void , const void) : 比较两个元素大小的函数指针,qsort 接受其返回值 -1/0/1

前提补充2:void*

  • void*的指针是非常宽容的,可以接收任意类型的地址
  • void*类型指针,无法直接解引用,无法++(++的优先级比较高)
  • 使用时需要强制转换成特定指针
//解析void*指针类型
int main()
{
	char ch = 'w';
	int i = 20;
	
	//void*的指针是非常宽容的,可以接收任意类型的地址!!
	void* p = &ch;
	p = &i;
	
	//void*类型指针,解引用的错误用法
	//1.无法直接解引用
	//*p = 200;//err
	//2.无法++,++的优先级比较高,不能用强制转换
	//p++;//err
	//(char*)p++;

	//正确做法
	*(int*)p = 200;//ok,强制类型转换一下就可以了
	
	return 0;
}

前提补充3:解析比较类型

想要解决冒泡的单一类型的问题,传参问题解决了

下一个问题,我们知道:
2个整形用关系运算符比较大小
2个字符串,使用strcmp比较大小
2个结构体,也得制定比较方式…

思路:设计一个函数,需要比较什么类型就传进去,以相应的类型进行比较
参照 qsort 的最后一个参数

// int 的比较
/*
	if (*(int*)e1 > *(int*)e2)
		return 1;
	if (*(int*)e1 < *(int*)e2)
		return -1;
	else
		return 0;
*/

int cmp_int(const void* e1, const void* e2)// 要比较的两个元素
{
	return (*(int*)e1 - *(int*)e2);		// 升序
	//return (*(int*)e2 - *(int*)e1);	// 降序
}

/*测试 qsort 函数排序 int 数据
void test()
{
	int arr[] = { 2,1,3,7,5,6,8,4,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]),cmp_int);
	print(arr, sz);
}*/ 

// struct 成员的 比较
/*
	struct stu
	{
		char name[20];
		int age;
	};
*/

cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e1)->name);
}

cmp_stu_by_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e1)->age);
}

/*测试 qsort 函数排序 struct 数据
void test()
{
	struct stu s[] = { {"zhangsan",25},{"lisi",21},{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按照名字比较
	//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	//按照年龄比较
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
*/
//-------------------------qsort()函数用法测试代码结束-----

将 bubble 改造至能排序任意数据类型

模仿 qsort 的参数类型,以 bubble 算法,使其能对任意数据类型排序。

// 交换
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
} 

// 排序主体功能
void bubble_sort_all(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	int j = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)//sz-1,是因为拿一个数字比,比较sz-1次
	{
		//一趟冒泡排序的过程
		int aim = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换 - 交换 cmp 种比较两个元素地址的每一个字节
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				aim = 1;
			}
		}
		if (aim == 0)
			break;
	}
}



🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值