在代码逻辑执行过程中,有些逻辑需要进行预留,不能立即实现,这种情况下我们就用到了回调函数,只定义要做什么,没规定必须要怎么做。

        通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

一、核心概念理解

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;
}

总结

  1. 核心逻辑:回调函数的本质是函数指针,注册回调就是保存函数地址,触发回调就是通过地址调用函数;
  2. 关键注意点:必须检查函数指针是否为 NULL,避免空指针访问导致程序崩溃;
  3. 使用场景:常用于框架设计(如驱动、网络库)、事件驱动编程、异步处理等,核心价值是解耦(框架只定义接口,用户实现具体逻辑)。

代码示例

更多推荐