目录
- 一、回调函数
- 二、快速排序函数 qsort
- 1. qsort() 函数原型
- 2. 使用 qsort() 函数
- 三、仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
- 1. 函数原型
- 2. 函数设计思路
一、回调函数
在 C 语言中,回调函数是一种通过函数指针调用的函数,也就是一个被作为参数传递的函数。在特定的事件或条件发生时,主程序可以调用这个被传递进来的函数来执行特定的操作。
在指针进阶(二)(C 语言)中的最后我们使用函数指针数组模拟了简单的计算器,我们把其中的输入计算部分,封装成了一个函数 calc(),需要传入对应操作的函数地址,然后在 calc() 函数中通过函数指针来调用相应的函数。而这个被调用的函数就被称为回调函数。而在这里使用回调函数的主要作用是减少代码的冗余,如果不使用回调函数,那么 switch 语句部分就会是如下代码:
switch (input){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}
可以看到上述代码的前四个选项中,除了调用的函数不同,其他代码部分都是相同的。所以,前四个选项的功能设计成一个函数,根据不同的功能传递不同的函数指针,使用回调函数来完成相应的功能。这样减少了代码的冗余,提升了代码的可读性。
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;default:printf("选择错误\n");break;}
二、快速排序函数 qsort
C 语言标准库中的 qsort() 函数是专门用来给数组排序的,且只要给它提供一个合适的比较函数,它可以排序任意类型的数组。
1. qsort() 函数原型
void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
可以看到上面函数原型中的第一个参数 base 是 void* 指针,因为该函数预先并不知道你要排序哪种类型的数组,所以只能使用 void* 指针来进行接收待排序数组的首地址。第二个参数 nmemb 的类型是 size_t 用来接收数组元素的个数。第三个参数 size 的类型是 size_t 用来接收数组每个元素的大小,单位字节。最后一个参数 compar 的类型是指向需要两个 const void* 的指针作为参数且返回值为 int 的数组,该指针的作用是回调我们传递的比较函数,因为 qsort 函数预先不知道我们需要比较的类型,可能是 int、double,也可能是字符串或者结构体。
2. 使用 qsort() 函数
接下来分别使用 qsort() 函数来排序 int 数组、double 数组、字符串数组以及结构体数组。需要分别为这四种类型设计四个比较函数:
// 头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 学生结构体
typedef struct Stu
{char name[20];char age;
}Stu;// int 类型比较
int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*) e2;
}// double 类型比较
int cmp_double(const void* e1, const void* e2)
{return *(double*)e1 - *(double*)e2;
}// 字符串类型比较
int cmp_str(const void* e1, const void* e2)
{return strcmp((char*)e1, (char*)e2);
}// Stu 结构比较
int cmp_Stu(const void* e1, const void* e2)
{return (*(Stu*)e1).age - (*(Stu*)e2).age;
}
使用 qsort() 函数需要包含头文件 stdlib.h,使用字符串比较函数 strcmp() 需要包含头文件 string.h。下面是创建所需变量和排序的代码:
int main()
{// int 类型排序int arr_i[10] = { 1,3,5,7,9,2,4,6,8,0 };int sz = sizeof(arr_i) / sizeof(arr_i[0]);printf("排序前: ");print_arr_i(arr_i, sz);printf("排序后: ");qsort(arr_i, sz, sizeof(int), cmp_int);print_arr_i(arr_i, sz);// double 类型排序double arr_d[] = { 1.1,9.9,8.8,3.3,6.6,7.7,4.4,5.5 };sz = sizeof(arr_d) / sizeof(arr_d[0]);printf("排序前: ");print_arr_d(arr_d, sz);printf("排序后: ");qsort(arr_d, sz, sizeof(double), cmp_double);print_arr_d(arr_d, sz);// 字符串类型排序char arr_str[5][20] = {"abcde111","ecdba222","mnbvd333","iknhk444","cvbnm555"};sz = sizeof(arr_str) / sizeof(arr_str[0]);printf("\n排序前:\n");print_arr_str(arr_str, sz);printf("排序后:\n");qsort(arr_str, sz, sizeof(arr_str[0]), cmp_str);print_arr_str(arr_str, sz);// Stu 结构类型排序Stu arr_Stu[3] = {{"张三", 20},{"王五", 19},{"李四", 18}};sz = sizeof(arr_Stu) / sizeof(arr_Stu[0]);printf("\n排序前:\n");print_arr_Stu(arr_Stu, sz);printf("排序后:\n");qsort(arr_Stu, sz, sizeof(arr_Stu[0]), cmp_Stu);print_arr_Stu(arr_Stu, sz);return 0;
}
程序的运行结果如下:
三、仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
1. 函数原型
// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2));
这里需要知道,我们实现的 bubble_sort() 函数和库函数 qsort() 只是排序使用的算法不同,其他的思路都是一样的,所以函数的参数和返回类型也是一样的。
2. 函数设计思路
进入函数 bubble_sort 以后,冒泡排序的整体是不变的,依旧是嵌套循环,外层是排序次数,内层是比较次数。但是由于预先不知道传入数组的元素类型,所以我们面临如下两个问题:
(1)在比较的时候如何传递每个元素的地址
(2)如何交换相邻的元素
首先,我们知道传入数组的首地址以及数组每个元素的大小。那么我们可以把数组首地址强制类型转换为 char* 类型,然后加上数组元素大小的整数倍,那么就可以得到数组的每个元素的地址了。思路如下图:
所以,就可以写出如下代码:
// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2))
{int i;for (i = 0; i < n - 1; ++i) // 外层排序 n-1 轮{int j;for (j = 0; j < n - i - 1; ++j) // 内层比较 n-i-1 次{if (cmp((char*)start + j * size, (char*)start + (j + 1) * size) > 0) // 排升序{// 交换}}}
}
那么如何交换两个元素?我们知道两个元素的首字节地址,也知道两个元素的大小,那么我们可以通过交换两个元素的每个字节来实现两个元素的交换。思路如下图:
按理来说应该把这个交换功能设计成一个函数,但是这里为了方便观看,就放在函数内部了:
// 仿照 qsort 函数设计一个可以排序任意类型数组的冒泡函数
void bubble_sort(void* start, size_t n, size_t size, int (*cmp)(const void* e1, const void* e2))
{int i;for (i = 0; i < n - 1; ++i) // 外层排序 n-1 轮{int j;for (j = 0; j < n - i - 1; ++j) // 内层比较 n-i-1 次{if (cmp((char*)start + j * size, (char*)start + (j + 1) * size) > 0) // 排升序{// 交换int k;for (k = 0; k < size; ++k){char tmp = *((char*)start + j * size + k);*((char*)start + j * size + k) = *((char*)start + (j + 1) * size + k);*((char*)start + (j + 1) * size + k) = tmp;}}}}
}
接下来使用前面 qsort() 函数排序的数据检验我们所写的 bubble_sort() 函数,只需要把调用的函数名 qsort 改成 bubble_sort 即可:
int main()
{// int 类型排序int arr_i[10] = { 1,3,5,7,9,2,4,6,8,0 };int sz = sizeof(arr_i) / sizeof(arr_i[0]);printf("排序前: ");print_arr_i(arr_i, sz);printf("排序后: ");bubble_sort(arr_i, sz, sizeof(int), cmp_int);print_arr_i(arr_i, sz);// double 类型排序double arr_d[] = { 1.1,9.9,8.8,3.3,6.6,7.7,4.4,5.5 };sz = sizeof(arr_d) / sizeof(arr_d[0]);printf("排序前: ");print_arr_d(arr_d, sz);printf("排序后: ");bubble_sort(arr_d, sz, sizeof(double), cmp_double);print_arr_d(arr_d, sz);// 字符串类型排序char arr_str[5][20] = {"abcde111","ecdba222","mnbvd333","iknhk444","cvbnm555"};sz = sizeof(arr_str) / sizeof(arr_str[0]);printf("\n排序前:\n");print_arr_str(arr_str, sz);printf("排序后:\n");bubble_sort(arr_str, sz, sizeof(arr_str[0]), cmp_str);print_arr_str(arr_str, sz);// Stu 结构类型排序Stu arr_Stu[3] = {{"张三", 20},{"王五", 19},{"李四", 18}};sz = sizeof(arr_Stu) / sizeof(arr_Stu[0]);printf("\n排序前:\n");print_arr_Stu(arr_Stu, sz);printf("排序后:\n");bubble_sort(arr_Stu, sz, sizeof(arr_Stu[0]), cmp_Stu);print_arr_Stu(arr_Stu, sz);return 0;
}
运行结果和使用 qsort() 函数时是一样的: