回调函数就是一个被作为参数传递的函数。在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语句中的部分会非常冗余,造成代码不够精炼。