电脑工场
白蓝主题五 · 清爽阅读
首页  > 软件入门

指针操作回调函数:C语言里那个“会自己动”的函数

你写过排序程序吗?比如把一串数字从小到大排好。标准库里的 qsort 函数,用起来就一行:qsort(arr, n, sizeof(int), cmp);。可你有没有想过,它怎么知道你是想升序还是降序?它压根不认识你的 cmp 函数,却能调它、用它、靠它做决定——这背后,就是指针操作回调函数在干活。

回调函数不是“被调用”,是“被记住”

普通函数是“我写好,你来调”;回调函数是“你先写好,我记下地址,等需要时再反过头来调你”。关键就在那个“记下地址”——靠的是函数指针。

比如这个比较函数:

int compare_ints(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}

它本身没被谁主动执行。真正起作用的是把它“交出去”:

qsort(numbers, 5, sizeof(int), compare_ints); // 注意:这里没加(),传的是地址

这行代码里,compare_ints 没带括号,编译器就知道:这不是要执行它,而是要取它的内存地址。这个地址,就被 qsort 存在一个函数指针变量里,等内部循环到两个元素要比较时,才解引用指针,跳过去执行。

自己动手写一个回调场景

假设你在写一个简易日志系统,想让不同模块按各自习惯输出日志:有的打到控制台,有的存进文件,有的发到网络。不改日志函数本身,怎么做到?

// 定义函数指针类型:指向一个接受const char*参数、返回void的函数
typedef void (*log_handler_t)(const char*);

// 日志主函数,接收一个函数指针作为“处理方式”
void log_message(const char* msg, log_handler_t handler) {
if (handler) {
handler(msg); // 这里才是真正的回调:通过指针跳转执行
}
}

// 三种不同的处理方式
void print_to_console(const char* msg) {
printf("[CONSOLE] %s\n", msg);
}

void save_to_file(const char* msg) {
FILE* f = fopen("log.txt", "a");
if (f) {
fprintf(f, "[FILE] %s\n", msg);
fclose(f);
}
}

// 使用时自由切换
log_message("用户登录成功", print_to_console);
log_message("配置加载完成", save_to_file);

看到没?log_message 完全不知道也不关心你传进来的是哪个函数,它只管拿着指针“啪”一下跳过去。这就是回调的灵活性来源——行为和逻辑分离,靠指针搭桥。

为什么非得用指针?不能直接传函数名?

C语言里,函数名在绝大多数上下文中会自动退化为函数指针(就像数组名退化为指针一样)。所以 compare_ints&compare_ints 在传参时效果一样。但理解“传的是地址”很重要:它意味着你可以动态换人——比如根据配置读取一个标志位,决定调 encrypt_data 还是 decrypt_data,只要它们签名一致,就能塞进同一个函数指针变量里。

再举个实在例子:单片机开发中,定时器中断触发后,硬件会跳到固定地址执行。但你不可能每次改固件去硬编码处理逻辑。实际做法是:定义一个函数指针变量 timer_callback,初始化时把它指向你写的 on_timer_tick 函数;中断发生时,CPU 执行的其实是类似 if (timer_callback) timer_callback(); 的语句——指针在这里,就是软硬件之间的活接口。