什么是回调函数?
一句话解释:

(图片来源网络,侵删)
回调函数就是你定义一个函数,然后把这个函数的地址(指针)作为参数传递给另一个函数,稍后,另一个函数会在合适的时机“回调”你定义的这个函数。
生活中的比喻:
想象一下你去餐厅吃饭:
- 你(调用者) 点了一道菜。
- 你把菜单(包含你的订单信息)交给服务员(被调用者)。
- 服务员把菜单送到厨房。
- 厨房(执行者)做菜,这个过程需要时间,你不能在厨房门口等着。
- 做好菜后,服务员把菜端回到你的桌上,这个“把菜端回来”的动作,回调”。
在这个比喻里:

(图片来源网络,侵删)
- 你:主程序
- 服务员:一个需要执行耗时操作的函数(一个网络请求库)
- 厨房:真正执行耗时操作的地方
- 你的订单(包含“做好后通知我”的意愿):回调函数的指针
- 服务员通知你菜好了:执行回调函数
为什么需要回调函数?
回调函数主要有三大好处:
- 实现异步操作:这是最重要的用途,当一个函数需要执行一个耗时的操作(如网络请求、文件读写、数据库查询)时,如果使用同步方式,主程序就会被阻塞,等待操作完成,使用回调,主程序可以立即继续执行其他任务,等耗时操作完成后,再通过回调函数通知主程序结果。
- 定制化功能:你可以将通用的算法框架写好,但把其中某些具体的行为(比如如何比较两个元素、如何打印一个元素)留给调用者去定义,C 语言标准库中的
qsort函数,它需要一个比较函数作为回调,来决定如何对数组进行排序。 - 事件驱动编程:在图形界面编程或网络编程中,回调非常普遍,你设置一个“当按钮被点击时”的事件处理器,这个事件处理器就是一个回调函数,操作系统或框架会在检测到按钮点击事件时,自动调用你的回调函数。
如何在 C 语言中实现回调函数?
C 语言中的回调函数主要通过函数指针来实现。
步骤 1:定义回调函数的类型
你需要定义一个函数指针类型,这个类型描述了你的回调函数应该“长什么样”(即它的返回值类型和参数列表)。
// 定义一个函数指针类型 MyCallback // 它指向一个函数,该函数接收一个 int 参数,返回 void typedef void (*MyCallback)(int result);
步骤 2:实现回调函数
你实现一个符合上述函数指针类型的函数。

(图片来源网络,侵删)
// 这是一个具体的回调函数实现
void my_completion_handler(int result) {
printf("操作完成!结果是: %d\n", result);
}
步骤 3:定义一个接受回调函数的函数
创建一个函数,它将执行某个任务,并在任务完成时调用回调函数。
// 这个函数执行一个耗时操作,完成后调用回调函数
// 注意它接收一个 MyCallback 类型的参数
void do_some_async_operation(MyCallback callback) {
printf("开始执行耗时操作...\n");
// 模拟耗时操作(比如网络请求、计算等)
for (int i = 0; i < 5; i++) {
printf("处理中...\n");
sleep(1); // 在 Windows 上可以用 Sleep(1000);
}
int operation_result = 42; // 假设操作结果是 42
printf("操作即将结束,准备回调...\n");
// 调用传入的回调函数,并传递结果
callback(operation_result);
}
步骤 4:调用主函数,传递回调函数
在 main 函数中,你调用 do_some_async_operation,并将你的回调函数 my_completion_handler 传递给它。
int main() {
printf("主程序开始,\n");
// 调用函数,并传入回调函数 my_completion_handler
// my_completion_handler 的地址被传递过去
do_some_async_operation(my_completion_handler);
printf("主程序结束,\n");
return 0;
}
完整代码示例 (基础版)
#include <stdio.h>
#include <unistd.h> // for sleep (Linux/macOS)
// #include <windows.h> // for Sleep (Windows)
// 1. 定义回调函数的类型
typedef void (*CompletionCallback)(int result);
// 2. 实现一个具体的回调函数
void on_operation_complete(int result) {
printf("[回调] 收到结果: %d\n", result);
}
// 3. 定义一个接受回调的函数
void perform_task(CompletionCallback callback) {
printf("[任务] 开始执行任务...\n");
// 模拟耗时操作
printf("[任务] 正在处理数据...\n");
sleep(2); // Windows: Sleep(2000);
int final_result = 100;
printf("[任务] 任务处理完毕,准备通知调用者,\n");
// 4. 调用回调函数
if (callback != NULL) {
callback(final_result);
}
}
// 5. main 函数
int main() {
printf("[主] 程序启动,\n");
// 调用 perform_task,并将回调函数 on_operation_complete 传递进去
perform_task(on_operation_complete);
printf("[主] 程序结束,\n");
return 0;
}
编译和运行 (Linux/macOS):
gcc callback_example.c -o callback_example ./callback_example
预期输出:
[主] 程序启动。
[任务] 开始执行任务...
[任务] 正在处理数据...
[任务] 任务处理完毕,准备通知调用者。
[回调] 收到结果: 100
[主] 程序结束。
更复杂的回调:带上下文的回调
回调函数不仅需要知道操作结果,还需要访问一些额外的数据,比如它自己的状态、结构体等,这时,我们可以使用 void* 指针来传递“上下文”(Context)。
修改后的代码示例 (带上下文)
#include <stdio.h>
#include <unistd.h>
// 1. 回调函数类型现在增加一个 void* context 参数
typedef void (*CompletionCallbackWithContext)(int result, void* context);
// 2. 定义一个结构体作为我们的上下文
typedef struct {
const char* user_name;
int user_id;
} UserContext;
// 3. 修改后的回调函数,现在它可以访问上下文数据
void on_operation_complete_with_context(int result, void* context) {
UserContext* user = (UserContext*)context;
printf("[回调] 用户 %s (ID: %d) 的操作完成,结果: %d\n",
user->user_name, user->user_id, result);
}
// 4. 修改后的任务函数,现在接受一个上下文指针
void perform_task_with_context(CompletionCallbackWithContext callback, void* context) {
printf("[任务] 开始执行任务(带上下文)...\n");
printf("[任务] 正在处理数据...\n");
sleep(2);
int final_result = 200;
printf("[任务] 任务处理完毕,准备通知调用者,\n");
if (callback != NULL) {
callback(final_result, context);
}
}
// 5. main 函数
int main() {
printf("[主] 程序启动,\n");
// 创建并初始化上下文
UserContext my_context = {
.user_name = "Alice",
.user_id = 12345
};
// 调用 perform_task_with_context,同时传递回调函数和上下文
perform_task_with_context(on_operation_complete_with_context, &my_context);
printf("[主] 程序结束,\n");
return 0;
}
预期输出:
[主] 程序启动。
[任务] 开始执行任务(带上下文)...
[任务] 正在处理数据...
[任务] 任务处理完毕,准备通知调用者。
[回调] 用户 Alice (ID: 12345) 的操作完成,结果: 200
[主] 程序结束。
这种方式非常强大,是现代 C 语言库(如 libcurl, OpenSSL, GObject 等)中实现回调的标准模式。
经典应用场景
qsort - 标准库中的回调
qsort 是 C 标准库中回调函数最经典的例子,它需要一个比较函数来决定两个元素的顺序。
#include <stdio.h>
#include <stdlib.h>
// qsort 的回调函数:比较两个整数
// 参数是 const void* 指针,需要强制转换
int compare_ints(const void* a, const void* b) {
int arg1 = *(const int*)a;
int arg2 = *(const int*)b;
if (arg1 < arg2) return -1;
if (arg1 > arg2) return 1;
return 0;
}
int main() {
int values[] = { 88, 56, 100, 2, 25 };
int n = sizeof(values) / sizeof(values[0]);
// qsort 的第四个参数就是我们传入的回调函数
qsort(values, n, sizeof(int), compare_ints);
for (int i = 0; i < n; i++) {
printf("%d ", values[i]);
}
printf("\n"); // 输出: 2 25 56 88 100
return 0;
}
信号处理 - POSIX 中的回调
在 POSIX 系统中,使用 signal 或 sigaction 注册信号处理函数时,处理函数本身就是一个回调。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 这就是一个信号处理回调函数
void handle_sigint(int sig) {
printf("\n捕获到信号 %d! 程序即将退出,\n", sig);
exit(1);
}
int main() {
// 注册 SIGINT (Ctrl+C) 的处理函数
// 当 Ctrl+C 被按下时,操作系统会“回调” handle_sigint 函数
signal(SIGINT, handle_sigint);
printf("程序正在运行,按下 Ctrl+C 来测试...\n");
while (1) {
sleep(1);
}
return 0;
}
| 概念 | 描述 | 示例 |
|---|---|---|
| 回调函数 | 将一个函数作为参数传递给另一个函数,供后者在特定时机调用。 | do_something(my_function) |
| 函数指针 | 存储函数内存地址的指针,是实现回调的基础。 | void (*func_ptr)(int) |
| 回调类型 | 使用 typedef 为函数指针定义一个别名,使代码更清晰。 |
typedef void (*MyCallback)(int); |
| 上下文 | 通过 void* 指针向回调函数传递额外的自定义数据。 |
void my_callback(int result, void* ctx); |
掌握回调函数是迈向高级 C 语言编程的关键一步,它让你能够编写出更健壮、更灵活、更符合现代软件架构(如事件驱动、异步编程)的代码。
