C 函数指针入门
更新时间:2026-08-26。本文是
languages/c/主题入门层第 40 篇。指针指向变量不稀奇,指向函数才是 C 的进阶玩法:把函数当参数传来传去(回调)、用函数指针数组做命令分发——这是很多"高级" C 代码的秘密武器。
本文要回答的问题
- 函数指针怎么声明、怎么调用?
- 什么是回调函数?
qsort怎么用? - 函数指针数组能干嘛?
一、声明与调用
c
int add(int a, int b) { return a + b; }
int (*fp)(int, int); // fp 是指向"返回 int、收两个 int"的函数的指针
fp = add; // 函数名就是函数地址(类似数组名衰减)
int r = fp(3, 4); // 调用:等价 (*fp)(3, 4),两种写法都行
printf("%d\n", r); // 7读声明:int (*fp)(int, int) —— fp 是 (*fp)(先解引用)得到函数,签名为 int(int, int)。
c
int (*fp)(int, int); // 函数指针
int *fp2(int, int); // 函数:返回 int*!(没有括号,完全不同)括号是关键:(*fp) 表示"fp 是个指针,指向函数";*fp2(...) 是"fp2 是函数,返回指针"。差一对括号,天差地别。
二、回调函数:把行为传进函数
"回调"就是把函数指针作为参数传给别人,别人在合适时机调用你:
c
#include <stdio.h>
void repeat(int n, void (*action)(int)) { // 收一个函数指针
for (int i = 0; i < n; i++) {
action(i); // 回调:调用传入的函数
}
}
void say(int i) { printf("say %d\n", i); }
void shout(int i) { printf("SHOUT %d\n", i); }
int main(void) {
repeat(3, say); // 传"行为"进去
repeat(2, shout);
return 0;
}价值:repeat 不关心"做什么",只负责"怎么循环"——行为和结构解耦。事件系统、定时器、排序比较、遍历回调,全是这个套路。
三、经典案例:qsort 的比较函数
c
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a; // void* 转回 int*
int y = *(const int *)b;
return (x > y) - (x < y); // 返回负/0/正
}
int main(void) {
int arr[] = {5, 2, 8, 1, 9, 3};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), cmp_int); // 传入比较函数
for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n"); // 1 2 3 5 8 9
return 0;
}qsort 需要你提供"比较规则"(cmp_int),它只负责排序算法——同一套排序函数,换比较函数就换排序规则(数字/字符串/结构体字段都可以)。标准库的通用性就来自函数指针。
四、函数指针数组:命令分发
把一组函数放进数组,按下标调用——实现"命令表":
c
void cmd_add(void) { printf("执行新增\n"); }
void cmd_del(void) { printf("执行删除\n"); }
void cmd_quit(void) { printf("退出\n"); }
void (*commands[])(void) = {cmd_add, cmd_del, cmd_quit};
// commands[0] = cmd_add ...
int main(void) {
int choice = 1; // 模拟用户输入
if (choice >= 0 && choice < 3) {
commands[choice](); // 按下标分发
}
return 0;
}对比 if-else/switch:命令多了以后,函数指针数组把"分发逻辑"变成数据(表驱动),新增命令 = 数组加一项。大型系统里这种表驱动模式很常见。
五、什么时候用函数指针
| 场景 | 理由 |
|---|---|
| 回调(qsort、遍历、事件) | 结构通用,行为可替换 |
| 命令分发(表驱动) | 消除长 switch,扩展方便 |
| 策略模式(算法可换) | 运行时切换实现 |
| 钩子/插件 | 预留扩展点 |
别滥用:函数指针让代码间接,难调试(gdb 里是一层跳转)。能用普通调用就说清意图的场景,别为了"高级"而用。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 声明少括号 | 变成返回指针的函数 | (*fp) 记得括号 |
| 签名不匹配 | 编译警告/崩溃 | 类型严格对应 |
| 调用未初始化函数指针 | 段错误 | 赋值后再调用 |
| 忘解引用(等价,可省略) | 风格不一 | 统一 fp(...) |
| 回调里忘处理边界 | 越界/空指针 | 回调内判空 |
七、与本站主线衔接
- 函数指针的高级应用(回调表、面向对象模拟),见高手层函数指针与回调进阶;
- 函数指针在汇编里就是"间接调用"(
call *%rax),见与汇编、性能剖析衔接; - 用函数指针做策略模式,见排序与查找的工程实践。
一句话总结
函数指针 = "把函数当地址存起来",声明关键是 (*fp)(参数) 的括号;回调让结构和行为解耦(qsort 传比较函数就是样板);函数指针数组 = 表驱动命令分发——三个用途记住,C 就多了一件利器。