目录
- [1. 回调函数](#1. 回调函数)
- [2. qsort 使用举例](#2. qsort 使用举例)
-
- [2.1 使用qsort来排序整型数组](#2.1 使用qsort来排序整型数组)
- [2.2 使用qsort来排序结构体数据](#2.2 使用qsort来排序结构体数据)
- 3.qsort函数的模拟实现
1. 回调函数
- 定义
回调函数是一种 函数指针(地址) 作为参数传递给另一个函数的机制。 - 调用方式
- 回调函数本身 不是直接由程序显式调用 的。
- 它是在特定的 事件或条件发生时,由被传入的另一方函数调用的。
- 使用目的
回调函数通常用于 对特定事件或条件做响应。也就是,当某件事情发生或某个条件满足时,程序会触发回调函数执行对应的逻辑。 - 特点总结
- 传递灵活:可以在运行时把不同的函数作为参数传入,动态改变行为。
- 事件驱动:常用于事件驱动编程,如 GUI 操作、异步操作、定时器等。
- 解耦合:调用者只负责触发事件,不需要知道回调函数的具体实现,提高代码可维护性。
c
// 定义回调函数:接受两个整数并返回整数
int Add(int x, int y)
{
return x + y;
}
// Compute函数接受两个整数和一个回调函数指针
int Compute(int a, int b, int (*p)(int, int))
{
// 调用回调函数 p,实际调用的是 Add
return p(a, b);
}
int main()
{
//将Add函数作为回调函数传入Compute
int r = Compute(2, 3, Add);
printf("%d\n", r); // 输出 5
return 0;
}

这里需要用到函数指针的知识,知识点回顾:C语言指针深入浅出4。
学习完回调函数后,我们就又可以改写在C语言指针深入浅出4中讲解转移表(简易计算器的实现)的代码。

代码实现:
c
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 calc(int (*p)(int, int))
{
int x = 0;
int y = 0;
printf("请输入两个数字:");
scanf("%d %d", &x, &y);
int r = p(x, y); //调用的是对应的函数,被调用的函数就是回调函数
printf("结果是:%d\n", r);
}
int main()
{
int r = 0;
int input = 1;
menu();
do
{
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;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
下面我再通过图片的方式对于这段代码中回调函数的使用进行讲解:

2. qsort 使用举例
qsort 是 C 标准库提供的通用排序函数 ,用于对数组中的任意类型元素进行排序。它内部使用快速排序(Quick Sort)算法,但只需要提供数组和比较方法即可,qsort的返回值是void类型。
qsort函数原型:

下面我来对qsort函数中的参数逐一进行说明:
void *base- 指向要排序数组的首元素的指针。
- 由于是通用排序,类型用
void*表示,可以指向任意类型 的数组。小tip:void*也被称为泛型指针,在C语言深入浅出1中讲解了void*指针相关内容。
size_t num- 数组中元素的数量。
size_t size- 数组中每个元素占用的字节数。
int (*compar)(const void *, const void *)- 比较函数的指针,也叫回调函数。
- 函数接收两个元素的指针,返回值表示这两个元素的相对顺序:
< 0:第一个元素排在前0:两个元素相等> 0:第一个元素排在后
- 这个函数决定了
qsort的排序规则。
更加大白话的理解:函数的返回值能体现出p1和p2指向的数据的大小。
p1指向的数据 > p2指向的数据, 返回 > 0的数字
p1指向的数据 < p2指向的数据, 返回 < 0的数字
p1指向的数据 == p2指向的数据, 返回 0
如果对于上述还有不理解的地方,下面这张图片可以加深对于qsort函数的理解:

2.1 使用qsort来排序整型数组
c
#include <stdio.h>
#include <stdlib.h> //使用qsort函数要包含头文件
int cmp_int(const void* p1, const void* p2)
{
// void* 类型的指针不能直接解引用取值
// 需要先强制转换为 int*,再解引用得到整数
return *((int*)p1) - *((int*)p2);
}
int main()
{
int arr[10] = { 8,7,6,9,5,4,3,1,2,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]), cmp_int); // 将比较函数 cmp_int 作为回调函数传给 qsort
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
return 0;
}
qsort函数工作原理示意图:

2.2 使用qsort来排序结构体数据
讲解之前,我们要讲解之前C语言操作符详解中还没讲解的结构体成员间接访问 的方式,我们还需要补充一个操作符:结构体指针访问操作符-> , 当我们通过结构体指针 访问结构体成员时,可以使用 -> 操作符。
结构体成员访问:
1. 结构体变量.成员名
2. 结构体指针->成员名
c
struct Stu
{
char name[20];
int age;
};
int main()
{
int a = 0;
struct Stu p1 = { "zhangsan",18 };
struct Stu* p2= &p1;
// 通过结构体指针访问结构体成员
printf("%s %d\n", p2->name, p2->age);
//修改结构体成员
p2->age = 20;
return 0;
}
讲解完这个后,C语言操作符详解 的介绍就正式告一段落了,接下来我们继续学习。
他由于下面我创建的结构体成员是char name[20]和int age,相应的比较方式也不同,我们一一进行学习。
第一种情况:比较年龄
c
#include <stdio.h>
#include <stdlib.h> //使用qsort函数要包含头文件
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
// p1 和 p2 指向的是结构体数组中的元素,所以需要强转为 struct Stu* 类型(结构体指针类型)
}
int main()
{
// 定义结构体数组,用来存储学生信息
struct Stu p[] = { {"zhangsan",21} ,{"lisi",19 }, {"wangwu",20 } };
//结构体的比较要分情况进行比较,在这个示例中要么比较年龄,要么比较名字
int sz = sizeof(p) / sizeof(p[0]);//计算数组的元素个数
qsort(p, sz, sizeof(p[0]), cmp_stu_by_age);
//qsort(结构体数组首地址, 学生个数, 每个学生结构体的大小, 按年龄比较);
for (int i = 0; i < sz; i++)
{
printf("%s %d\n", p[i].name,p[i].age);
//结构体成员的访问
}
return 0;
}

注意:在qsort函数的第三个参数时容易出错,因为比较的是年龄,但排序移动的是整个结构体,所以第三个参数必须是 sizeof(p[0]) ,不能是 sizeof(int)。
第二种情况:比较姓名
因为在 C 语言中,字符串不能直接使用 >、<、== 来比较内容大小,所以需要使用库函数 strcmp。strcmp 是字符串比较函数,用于按照字典序比较两个字符串的大小。
| 项目 | 说明 |
|---|---|
| 函数名 | strcmp |
| 头文件 | #include <string.h> |
| 函数原型 | int strcmp(const char* str1, const char* str2); |
| 功能 | 比较两个字符串的大小 |
| 比较方式 | 从两个字符串的第一个字符开始逐个比较,直到遇到不同字符或字符串结束标志 \0 |
| 比较规则 | 按字符的 ASCII 码值进行比较 |
| 注意事项 | strcmp 比较的是字符串内容,不是字符串长度 |
| 参数 | 含义 |
|---|---|
str1 |
第一个要比较的字符串 |
str2 |
第二个要比较的字符串 |
| 返回值 | 含义 |
|---|---|
< 0 |
str1 小于 str2 |
= 0 |
str1 等于 str2 |
> 0 |
str1 大于 str2 |
c
#include <stdio.h>
#include <string.h> //运用strcmp要包含头文件
#include <stdlib.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{
// 定义结构体数组,用来存储学生信息
struct Stu p[] = { {"zhangsan",29} ,{"lisi",30 }, {"wangwu",10 } };
int sz = sizeof(p) / sizeof(p[0]);//计算数组的元素个数
qsort(p, sz, sizeof(p[0]), cmp_stu_by_name);
for (int i = 0; i < sz; i++)
{
printf("%s %d\n", p[i].name, p[i].age);
}
return 0;
}

3.qsort函数的模拟实现
在通用排序算法中,通常有以下几步:

由于qsort函数内部使用快速排序(Quick Sort)算法,我们暂时没有讲到,我们就用冒泡的方式 来实现,冒泡排序的相关讲解:C语言深入浅出3。
注意事项:
1. base 是 void*类型,不能直接进行下标访问。
2. 由于 void* 不知道元素大小,所以要强转为 char*,按字节移动。
3. 第 j 个元素的地址为 : (char*)base + j * width

4. width 表示每个元素占用的字节数 。
5. cmp 用来比较两个元素的大小,传入的是两个元素的地址 。
6. Swap 按 width 个字节 交换,交换的是整个元素,因为我们将数组强制类型转化为char*,所以我们只能逐字节地进行交换 ,交换次数即为数组每个元素所占用的字节数width 。
7. 排序依据由 cmp 决定,交换大小由 width 决定。
代码实现:
c
#include <stdio.h>
#include <string.h>
void Swap(char* buf1, char* buf2, size_t width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
for (size_t i = 0; i < sz; i++)
{
for (size_t j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width , width);
}
}
}
}
int cmp_arr(const void* p1, const void* p2)
{
return *((int*)p1) - *((int*)p2);
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,0,3,2,1 };
struct Stu p[] = { {"zhangsan",21} ,{"lisi",19 }, {"wangwu",20 } };
int sz1 = sizeof(arr) / sizeof(arr[0]);
int sz2 = sizeof(p) / sizeof(p[0]);
bubble_sort(arr, sz1, sizeof(int), cmp_arr);
bubble_sort(p, sz2, sizeof(p[0]), cmp_stu_by_name);
for (int i = 0; i < sz1; i++)
printf("%d ", arr[i]);
printf("\n\n");
for (int i = 0; i < sz2;i++)
printf("%s %d\n", p[i].name, p[i].age);
return 0;
}

完