第15讲:深入理解指针(5)——回调函数与 qsort 深度解析
🧭 第15讲:深入理解指针(5)——回调函数与 qsort
深度解析 🔍🛠️
用函数指针实现“智能排序”与“事件响应”,掌握C语言的高阶编程艺术!
📚 目录
-
回调函数:程序的“事件响应机制” 🎯
-
qsort
使用举例:通用排序引擎 🔢📊 -
qsort
函数的模拟实现:从零构建通用排序 🔧 -
学习收获总结 ✅
回调函数:程序的“事件响应机制” 🎯
🧩 什么是回调函数?
回调函数(Callback Function) 是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。
📌 核心要点:
- 回调函数不是由函数实现方直接调用。
- 而是在特定的事件或条件发生时,由另外的一方调用,用于对该事件或条件进行响应。
🎯 为什么需要回调函数? 回顾第13讲中的计算器代码,你会发现红色框中的代码是重复出现的:
- 输入操作数
- 输出结果
虽然执行计算的逻辑不同(加、减、乘、除),但输入输出操作是完全冗余的。
有没有办法简化呢?
答案是:有!
因为只有“调用哪个计算函数”这一逻辑有差异,我们可以:
- 把计算函数的地址以参数形式传递给一个通用处理函数。
- 使用函数指针接收这个地址。
- 函数指针指向哪个函数,就调用哪个函数。
这,就是回调函数的精髓所在——将“变化的部分”作为参数传递,实现代码复用与逻辑解耦。
🔄 回调函数的经典应用场景:计算器优化
❌ 传统写法:冗余重复
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
}
📌 问题:输入输出逻辑重复,仅计算函数不同。
✅ 使用回调函数重构
#include <stdio.h>int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }// 回调函数:封装公共逻辑
void calc(int (*pf)(int, int)) {int ret = 0, x, y;printf("输入操作数:");scanf("%d %d", &x, &y);ret = pf(x, y); // 调用传入的函数printf("ret = %d\n", ret);
}int main() {int input = 1;do {printf("1:add 2:sub 3:mul 4:div 0:exit\n");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;
}
🎯 回调函数的优势
优势 | 说明 |
---|---|
✅ 消除重复代码 | 输入输出逻辑只写一次 |
✅ 提高可维护性 | 新增功能只需添加函数,无需修改主逻辑 |
✅ 实现逻辑解耦 | calc 不关心具体计算,只负责流程控制 |
✅ 支持运行时绑定 | 可根据条件动态选择调用哪个函数 |
🌟 应用场景:
- GUI 事件处理(点击、拖拽)
- 定时器回调
- 网络请求完成通知
- 排序比较函数(如
qsort
)
qsort
使用举例:通用排序引擎 🔢📊
qsort
是 C 标准库中的通用排序函数,使用回调函数实现自定义比较逻辑。
void qsort(void *base, size_t nmemb, size_t size,int (*compar)(const void *, const void *));
参数 | 说明 |
---|---|
base | 待排序数组首地址 |
nmemb | 元素个数 |
size | 每个元素大小(字节) |
compar | 比较函数指针 |
📌 比较函数返回值:
< 0
:e1 < e2
== 0
:e1 == e2
> 0
:e1 > e2
🔢 排序整型数组
#include <stdio.h>
#include <stdlib.h>// 比较函数:升序排列
int int_cmp(const void *p1, const void *p2) {return (*(int*)p1 - *(int*)p2);
}int main() {int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};int i;qsort(arr, 10, sizeof(int), int_cmp);for (i = 0; i < 10; i++) {printf("%d ", arr[i]);}printf("\n"); // 输出:0 1 2 3 4 5 6 7 8 9return 0;
}
📌
const void*
:通用指针类型,可接收任何类型地址。
📊 排序结构体数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct Stu {char name[20];int age;
};// 按年龄排序
int cmp_stu_by_age(const void *e1, const void *e2) {return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}// 按名字排序(字典序)
int cmp_stu_by_name(const void *e1, const void *e2) {return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}void test_sort_by_age() {struct Stu s[] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);for (int i = 0; i < sz; i++) {printf("%s %d\n", s[i].name, s[i].age);}
}void test_sort_by_name() {struct Stu s[] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);for (int i = 0; i < sz; i++) {printf("%s %d\n", s[i].name, s[i].age);}
}int main() {test_sort_by_age(); // 输出:wangwu 15, zhangsan 20, lisi 30test_sort_by_name(); // 输出:lisi 30, wangwu 15, zhangsan 20return 0;
}
📌
strcmp
:标准库函数,用于比较字符串。
qsort
函数的模拟实现:从零构建通用排序 🔧
通过冒泡排序模拟
qsort
,深入理解其工作原理。
🛠️ 核心工具函数:_swap
// 通用交换函数:交换任意类型数据
void _swap(void *p1, void *p2, int size) {char tmp;for (int i = 0; i < size; i++) {tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
📌
void\*
的作用:
- 通用指针,可指向任意类型数据
- 需强制转换为具体类型(如
char*
)进行操作 - 实现“类型无关”的通用算法
🔁 模拟 qsort
:冒泡排序实现
#include <stdio.h>// 比较函数:用于整型
int int_cmp(const void *p1, const void *p2) {return (*(int*)p1 - *(int*)p2);
}// 通用冒泡排序
void bubble_sort(void *base, int count, int size,int (*cmp)(const void *, const void *)) {for (int i = 0; i < count - 1; i++) {for (int j = 0; j < count - i - 1; j++) {// 计算当前元素和下一个元素的地址void *e1 = (char*)base + j * size;void *e2 = (char*)base + (j + 1) * size;// 使用回调函数比较if (cmp(e1, e2) > 0) {_swap(e1, e2, size); // 交换}}}
}int main() {int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};int i;bubble_sort(arr, 10, sizeof(int), int_cmp);for (i = 0; i < 10; i++) {printf("%d ", arr[i]);}printf("\n"); // 输出:0 1 2 3 4 5 6 7 8 9return 0;
}
🎯 模拟实现的关键点
技术点 | 说明 |
---|---|
void* base | 接收任意类型数组地址 |
(char*)base + j*size | 正确计算第 j 个元素地址 |
cmp(...) | 回调函数决定排序规则 |
_swap(...) | 通用交换,支持任意数据类型 |
✅ 扩展性:只需提供新的比较函数,即可排序任何类型数据。
✅ 学习收获总结
技能 | 掌握情况 |
---|---|
✅ 理解回调函数的概念与工作原理 | ✔️ |
✅ 能在项目中使用回调函数消除重复代码 | ✔️ |
✅ 掌握 qsort 的使用方法 | ✔️ |
✅ 能编写自定义比较函数(基础/结构体) | ✔️ |
✅ 理解 void* 的通用指针作用 | ✔️ |
✅ 能模拟实现 qsort (冒泡版本) | ✔️ |
✅ 掌握通用算法的设计思想 | ✔️ |
🎯 回调函数是C语言实现“多态”与“高阶函数”的核心机制。
你已掌握如何用函数指针编写可复用、可扩展的通用代码,这是迈向高级C程序员的关键一步!💪🔥
💬 需要本讲的 回调函数练习题 /
qsort
实战项目 / 动态内存预习资料?欢迎继续提问,我为你准备!