1. 回调函数是什么?
回调函数就是一个通过函数指针调用的函数
如果你把函数的指针(地址)作为参数传给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另一方调用的,用于对该事件的响应
如果觉得有点抽象的话我们看一个实例
实例:写一个计算器,实现加减乘除的功能
首先根据之前写扫雷游戏的经验,我们可以随手写出一段菜单,然后用 do_while 语句和 switch_case 语句写出菜单选择部分的代码,在将上一讲中的 add sub mul div 函数粘过来,填充到 switch_case 语句中,最后再简单丰富一下就完成了一个简易的计算器
#include<stdio.h>
void menu()
{
printf("##############################\n");
printf("###### 1.add 2.sub ######\n");
printf("###### 3.mul 4.div ######\n");
printf("###### 0.exit ######\n");
printf("##############################\n");
}
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 input;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
int x = 0, y = 0;
int ret = 0;
switch (input)
{
case 0:
printf("退出计算器\n");
break;
case 1:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = div(x, y);
printf("%d\n", ret);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}while (input);
return 0;
}
然后再运行一下试试
好的运行结果很成功,我们完成了简易计算器的代码,当然不要过度重视除法部分的简易函数,我知道那段代码太简陋了,甚至可以说是漏洞百出,但是我们讲解的重点不在那里。
言归正传,这段代码还有一个问题,就是太长了(长到我截屏根本截不下来),或者说过于冗余了,观察每个case语句中的内容,我们发现几乎都是一样的,只有调用的计算函数不同,那该怎么解决这个问题,简化这段代码呢?
我们可以再写一个函数 calc() 用这个函数作为管理者,填充在每个 case 中,管理着 case 语句中需要用到的计算函数
这样一来,我们case语句中的内容就简单多了,只需要一个cacl函数就可以解决问题
#include<stdio.h>
void menu()
{
printf("##############################\n");
printf("###### 1.add 2.sub ######\n");
printf("###### 3.mul 4.div ######\n");
printf("###### 0.exit ######\n");
printf("##############################\n");
}
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 cacl(int (*pf)(int, int))
{
int x = 0, y = 0;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出计算器\n");
break;
case 1:
cacl(add);
break;
case 2:
cacl(sub);
break;
case 3:
cacl(mul);
break;
case 4:
cacl(div);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}while (input);
return 0;
}
这样一来代码长度和冗余重复度就大大缩减了,再运行一下试试
好的结果很正确
在第二段代码中被calc()函数调用的计算函数在被调用的时候就成为回调函数,cacl()函数因为成为一个管理员的身份可以用来指挥回调哪种函数,所以其功能看起来更加强大和实用
当然,如果这段代码用函数指针数组写的话会更简单,用input的值作为下标,创建一个函数指针数组 int (*pf[ ])(int, int)={ NULL, add, sub, mul, div }; 用input做下标完成元素访问从而调用函数,这段代码读者可以结合上一节内容自己尝试一下。
2. qsort使用举例
qsort函数能够实现任意数据的排序(其底层逻辑是快速排序)
qsort是一个库函数,要引用 <stdlib.h>
下面介绍一下qsort函数的使用方法
void qsort(
void* base, //要排序的数组首元素地址
size_t num, //要排序的元素个数
size_t size, //要排序元素的字节大小
int (*compar)(const void*, const void*) //使用者自己写一个函数,用来表述元素的排序方案,p1应在p2之前返回<0的数,p1等同于p2返回0,p1应在p2之后返回>0的数
);
这是官网对于qsort函数的英文原版解释,可以参考一下
2.1 使用qsort排序整形数据(升序)
这段代码用qsort函数完成了对于整形数组的排序,int_cmp就是程序员为qsort函数最后一个参数写的函数,用来说明排序的规则:如果p1指向的元素比p2大就说明p1应放在在p2后面。
值得注意的是,在自定义排序规则函数时,其参数类型必须是 const void* 类型,后续如果要使用,记得将其类型强制转换成需要的指针类型。
2.2 使用qsort排序结构体数据
我要给出一个结构体数组,结构体中装的是学生的名字和年龄,排序这个数组
这段代码展示了结构体排序的思路,实际上并不困难,核心内容就是要自己规定排序方案是针对哪个结构体成员,这段代码中我规定了两种方案,第一种是按照年龄来排序,第二种是根据名字来排序,其他代码内容几乎不变,只是在强制类型转换时用到了结构体指针,紧接着用到了结构体成员访问操作符 -> 结构体方面的详细内容可以参考我的另一篇博客
可能你会注意到另一个点,在按名字比较的排序规则函数中我用到了一个库函数strcmp(),这是字符串比较操作符
int strcmp ( const char * str1, const char * str2 );
这个函数有两个参数,都是两个字符数组的首元素地址,函数会将这两个字符数组逐元素进行大小比较,出现某一方元素较大时就会返回值,当第一个字符串大时返回>0的数,当两个字符串相等时返回0,当第二个字符串大时返回<0的数。
这是官网原文,可以参考一下 strcmp - C++ Reference
3. qsort函数的模拟实现
接下来我将使用冒泡函数的排序逻辑实现模拟qsort的功能,使我的模拟函数可以将任意类型的数据进行排序
这段代码就是模拟函数主体的实现,函数的形参我是严格按照官网上给出的qsort函数的形参格式设置的,通过观察可以发现冒泡排序的主体逻辑框架并没有改变,只是在两个元素的交换条件和交换方式上做出改变。
因为无法预知传过来的数组元素的类型(一个元素占字节数的大小),于是我们采取一个字节一个字节操作的方式访问两个紧挨着的元素,通过将指针强制转换成 char*类型 完成一个字节一个字节操作的预设 (char*)base ,然后因为size变量是一个元素的字节长度,所以用 j*size 就可以设定要访问元素的起始字节指针,也就是说找到要访问元素的起始地址 (char*)base + j * size 这样就完成了对一个元素地址的访问。后面 j+1那一项意思大致相同,只是将指针指向了下一个元素,这样就找到了两个紧邻的元素,然后只要通过 compar 指针指向的函数就可以判断此时前一个元素要不要交换到后一个元素后面。
下面我们观察元素交换的函数 swap 是如何操作的
其实原理非常简单,还是因为无法预知 base数组 元素的字节长度,所以同样选择了一个字节一个字节的交换,当把size个字节都交换完成后两个数组元素也就交换了位置。
现在模拟qsort的函数就写完了,我们尝试用它给一个整形数组排序试试
完成了整形数组的排序,我们再尝试一下结构体数组的排序,这里我们只用学生名字作为排序依据了
好的,到这里我的模拟函数算是大功告成了,完成了我想要的效果:可以给任意只用类型的数组进行排序。并且我还可以自定义排序的方案,不再局限于给数字的大小进行排序,我可以通过自己设定的排序条件 compar 函数指针来任选排序方案,比如通过比较字符串大小,如果愿意还可以通过比较两个数据的字符串长度,或者字符串中某位置的字符大小,甚至可以比较某个数据的某个二进制位的表现来决定是否要将两个数组元素交换顺序,以此来达到我想要的元素顺序。
本节到这里就要结束了,本节虽然篇幅较短,大部分都是图片,但是有一定难度,要求我们对数组指针,函数指针,以及内存的一些知识有一定的掌握,希望读者朋友读完这篇博客后对于指针方面的内容能够有所启发。