C语言-回调函数与qsort库函数的模拟实现

回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现

回调函数的使用可以大大提升编程的效率,这使得它在现代编程中被非常多地使用。同时,有一些需求必须要使用回调函数来实现。

最著名的回调函数调用有C/C++标准库stdlib.h/cstdlib中的快速排序函数qsort和二分查找函数bsearch中都会要求的一个与strcmp类似的参数,用于设置数据的比较方法。

                                                                                                                        -摘自百度百科

库函数qsort模拟实现(stdlib.h / cstdlib)

void bubble_sort(void* base, unsigned sz, unsigned width, int (*cmp) (const void* e1, const void* e2))
{
	for (int i = 0; i < sz - 1; i++)    //一共进行sz-1次冒泡排序
	{
		for (int j = 0; j < sz - 1 - i; j++)
		{
			//1 2 3 4 5 6 
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				// 交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

利用冒泡排序模拟实现的qsort函数,排序的核心思想不同,但是效果以及基本原理相同

参数:

void* base:要排序的序列的首元素地址,用void*接收,这样使得函数对于任何类型的数据都可以排序

unsigned sz:要排序的元素个数。

unsigned width:排序的单个元素的所占字节数。(int:4)

int (*cmp) (const void* e1, const void* e2):此bubble_sort函数的使用者自定义的一个比较函数

将要排序的序列的首地址转化为char*,就可以根据j和width找到要比较的两个元素的地址。这里面转化为char*指针的目的仅仅是为了能够根据j和width找到要比较元素的首地址。

如下所示,cmp函数用void*指针接收传过来的指针,然后这个自己定义的函数之内再转化为你要比较的元素的指针类型,即可比较。

int cmp_int(const void* e1, const void* e2)
{
	return *((int*)e1) - *((int*)e2);
}
void swap(char* p1, char* p2, int width)
{
	//08 00 00 00    07 00 00 00
	for (int i = 0; i < width; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}

 此函数用来交换两个元素的值,这个方法非常巧妙,因为不知道要交换的元素的类型,这里给出两个要交换元素的首地址,直接用char*接收(并非void*接收),然后根据内存中的字节排列,逐个字节的内容进行交换。比如int数组中 8 7 两个元素要交换,在小端字节序环境下存储的是08 00 00 00 07 00 00 00。p1,p2接收的地址分别指向08,07这两个字节处,然后按照width循环更改每个char单位中的数据即可完成交换。

struct Stu
{
	char name[20];
	int age;
	double score;
};

void cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, (((struct Stu*)p2)->name));
}

void print_stu(struct Stu s[3], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d %lf\n", s[i].name, s[i].age, s[i].score);
	}
}

void test2()
{
	struct Stu s[3] = { {"yangzilong",18,90},{"xiaoqin",40,99},{"zhangsan",11,55} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	print_stu(s, sz);
}

void bubble_sort(void* base, unsigned sz, unsigned width, int (*cmp) (const void* e1, const void* e2))
{
	for (int i = 0; i < sz - 1; i++)    //一共进行sz-1次冒泡排序
	{
		for (int j = 0; j < sz - 1 - i; j++)
		{
			//1 2 3 4 5 6 
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				// 交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int main()
{
	test2();
	return 0;
}

以上是使用模拟实现qsort函数排序结构体数组的例子

void cmp_stu_by_name(const void* p1, const void* p2)这个函数是用户自己定义的,如果想转化为降序,则在前面加一个负号,或者对换p1,p2即可。

回调函数的用例2:

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");
}

void cal_(int (*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:>");
	scanf("%d%d", &x, &y);
	ret = p(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			cal_(Add);
			break;
		case 2:
			cal_(Sub);
			break;
		case 3:
			cal_(Mul);
			break;
		case 4:
			cal_(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

这里面的Add,Sub等函数就是一个被作为参数传递的函数,即回调函数。

如果不采用回调函数,也不使用函数指针数组的话,case语句中的部分会非常冗余,造成代码不够精炼。

你可能感兴趣的:(Bit,c语言,开发语言)