C语言学习之回调函数
在代码逻辑执行过程中,有些逻辑需要进行预留,不能立即实现,这种情况下我们就用到了回调函数,只定义要做什么,没规定必须要怎么做。
通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
一、核心概念理解
1. 回调函数是什么?
回调函数本质上是一个函数指针,你可以把它理解为:把一个函数的地址传递给另一个函数,让另一个函数在合适的时机 “回过来调用” 这个函数。
- 场景举例:你给客服留了电话(注册回调),客服处理完问题后主动打给你(调用回调)。
2. 注册回调函数的核心
“注册” 就是把回调函数的地址保存到某个地方(比如全局变量、结构体),让程序在需要的时候能找到并调用这个函数。
3.对于函数指针的typedef的定义
typedef 返回值类型 (*新类型名)(参数列表);
(*新类型名):*必须跟在新类型名前面,且必须加括号,标识这是 “函数指针类型”;去掉
*→ 变成 “函数类型”,失去指针属性,无法用于回调注册(回调的核心是传递函数地址,必须用指针)。
二、先拆解:typedef + 函数指针 = 类型别名
要理解这个 *,我们先把这句代码拆成 “无 typedef” 和 “有 typedef” 两步来看,核心是:* 是函数指针类型的固有组成部分。
1. 先看 “没有 typedef” 的函数指针声明
如果我们直接声明一个指向 “返回 void、接收 const char * 参数” 的函数指针变量,写法是:
// 声明一个函数指针变量 cb,它可以指向符合这个签名的函数
void (*cb)(const char* event_info);
- 这里的
(*cb)是关键:cb是一个指针,*明确告诉编译器 “cb 不是普通变量,是指向函数的指针”; - 括号
(*cb)不能少:如果写成void *cb(const char* event_info),编译器会把它解析成 “返回 void类型、接收 const char参数的函数 cb”,完全偏离了 “函数指针” 的本意。
2. 加上 typedef:给函数指针类型起别名
typedef 的作用是给一个已有的类型起一个新名字,而不是定义变量。我们把上面的 “变量名 cb” 换成 “别名 EventCallback”,就得到了你的代码:
// 把“void (*)(const char* event_info)”这个函数指针类型,命名为EventCallback
typedef void (*EventCallback)(const char* event_info);
- 这里的
*依然是函数指针类型的一部分:(*EventCallback)表示 “EventCallback 代表的是一个函数指针类型”; - 去掉
*会完全错误:如果写成typedef void (EventCallback)(const char* event_info);,编译器会把它解析成 “给‘返回 void、接收 const char * 参数的函数类型’起别名 EventCallback”,而非函数指针类型 —— 这是 “函数类型” 和 “函数指针类型” 的本质区别。

- 对于普通指针:
int*中的*表示 “指向 int 的指针”; - 对于函数指针:
(*EventCallback)中的*表示 “指向函数的指针”; - 简单说:
*是 “指针” 这个属性的标志,函数指针本质还是指针,所以必须带*来标识。
三、案例
#include <stdio.h>
#include <string.h>
// 1. 定义回调函数的类型(函数指针类型)
// 约定回调函数的参数和返回值:接收char*类型的事件信息,返回void
typedef void (*EventCallback)(const char* event_info);
// 2. 定义全局的回调函数指针(用于保存注册的回调函数)
static EventCallback g_callback = NULL;
// 3. 注册回调函数的接口(核心:把外部函数地址保存到全局指针)
void register_callback(EventCallback callback) {
if (callback != NULL) {
g_callback = callback;
printf("回调函数注册成功!\n");
} else {
printf("回调函数地址无效,注册失败!\n");
}
}
// 4. 模拟事件触发(时机到了,调用注册的回调函数)
void trigger_event(const char* event) {
printf("\n触发事件:%s\n", event);
// 检查是否已注册回调函数,避免空指针调用
if (g_callback != NULL) {
g_callback(event); // 调用回调函数
} else {
printf("未注册回调函数,无处理逻辑!\n");
}
}
// 5. 自定义的回调函数(用户实现具体逻辑)
void my_event_handler(const char* event_info) {
printf("自定义回调函数处理事件:%s\n", "【" event_info "】- 处理完成");
}
// 6. 另一个自定义回调函数(演示可注册不同回调)
void another_handler(const char* event_info) {
printf("另一个回调函数处理事件:%s\n", "《" event_info "》- 处理完毕");
}
int main() {
// 测试1:未注册回调时触发事件
trigger_event("系统启动");
// 测试2:注册第一个回调函数
register_callback(my_event_handler);
trigger_event("用户登录");
// 测试3:注册第二个回调函数(覆盖原有回调)
register_callback(another_handler);
trigger_event("数据保存");
// 测试4:注销回调(传入NULL)
register_callback(NULL);
trigger_event("系统关机");
return 0;
}
四、代码详解
1.函数指针类型定义:
typedef void (*EventCallback)(const char* event_info);
typedef给函数指针类型起别名EventCallback,简化后续使用;- 这个类型约定:回调函数必须是 “返回 void、接收 const char * 参数” 的函数。
2.注册函数:
void register_callback(EventCallback callback) { ... }
- 接收一个
EventCallback类型的函数指针,保存到全局变量g_callback中; - 增加了空指针检查,避免无效注册。
3.触发回调
if (g_callback != NULL) { g_callback(event); }
调用前必须检查指针是否为空,否则会导致程序崩溃(空指针访问)。
4.编译运行结果
触发事件:系统启动
未注册回调函数,无处理逻辑!
回调函数注册成功!
触发事件:用户登录
自定义回调函数处理事件:【用户登录】- 处理完成
回调函数注册成功!
触发事件:数据保存
另一个回调函数处理事件:《数据保存》- 处理完毕
回调函数地址无效,注册失败!
触发事件:系统关机
未注册回调函数,无处理逻辑!
四、进阶扩展(多回调注册)
5.进阶扩展(多回调注册)
上面的例子只能注册一个回调,实际开发中可能需要注册多个,可通过数组 / 链表实现:
#include <stdio.h>
#include <string.h>
#define MAX_CALLBACKS 5 // 最大回调数
// 回调函数类型
typedef void (*EventCallback)(const char* event_info);
// 回调数组+计数
static EventCallback g_callbacks[MAX_CALLBACKS] = {NULL};
static int g_callback_count = 0;
// 注册多个回调(不覆盖)
int register_callback(EventCallback callback) {
if (callback == NULL || g_callback_count >= MAX_CALLBACKS) {
return -1; // 注册失败
}
g_callbacks[g_callback_count++] = callback;
return 0; // 注册成功
}
// 触发所有注册的回调
void trigger_event(const char* event) {
printf("\n触发事件:%s\n", event);
for (int i = 0; i < g_callback_count; i++) {
if (g_callbacks[i] != NULL) {
g_callbacks[i](event);
}
}
}
// 自定义回调1
void handler1(const char* info) { printf("处理1:%s\n", info); }
// 自定义回调2
void handler2(const char* info) { printf("处理2:%s\n", info); }
int main() {
register_callback(handler1);
register_callback(handler2);
trigger_event("测试多回调");
return 0;
}
总结
- 核心逻辑:回调函数的本质是函数指针,注册回调就是保存函数地址,触发回调就是通过地址调用函数;
- 关键注意点:必须检查函数指针是否为 NULL,避免空指针访问导致程序崩溃;
- 使用场景:常用于框架设计(如驱动、网络库)、事件驱动编程、异步处理等,核心价值是解耦(框架只定义接口,用户实现具体逻辑)。

代码示例

更多推荐

所有评论(0)