一、函数指针的基本语法
函数指针的声明容易让人困惑,核心规则是:把函数声明中的函数名替换为 (*ptr)。
func_ptr_basic.c
/* 函数指针声明与调用 */ #include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int main(void) { /* 声明:指向接受两个 int 返回 int 的函数 */ int (*op)(int, int); op = add; /* 函数名退化为指针 */ printf("%d\n", op(3, 2)); /* 输出 5 */ op = sub; printf("%d\n", op(3, 2)); /* 输出 1 */ return 0; }
要点:
int *fp(int, int) 是「返回 int* 的函数」,而 int (*fp)(int, int) 才是「指向函数的指针」。括号不可省略。二、回调函数:以 qsort 为例
标准库的 qsort 是回调函数最经典的用例。用户传入比较逻辑,算法框架负责调度。
qsort_callback.c
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char name[32]; int score; } Student; /* 按分数升序 */ int cmp_score(const void *a, const void *b) { const Student *s1 = a, *s2 = b; return s1->score - s2->score; } /* 按名字字典序 */ int cmp_name(const void *a, const void *b) { const Student *s1 = a, *s2 = b; return strcmp(s1->name, s2->name); } int main(void) { Student cls[] = { {"Alice", 92}, {"Bob", 78}, {"Carol", 85} }; size_t n = sizeof(cls) / sizeof(cls[0]); qsort(cls, n, sizeof(Student), cmp_score); /* 同一组数据,换 cmp_name 即可按名排序 */ for (size_t i = 0; i < n; i++) printf("%s: %d\n", cls[i].name, cls[i].score); return 0; }
三、事件驱动:用函数指针实现简易信号槽
在嵌入式 GUI 或网络框架中,常用「注册-触发」模型解耦事件源与处理器。
event_loop.c
#include <stdio.h> #define MAX_HANDLERS 8 typedef void (*EventHandler)(const char *msg); static EventHandler handlers[MAX_HANDLERS]; static int hcount = 0; void register_handler(EventHandler h) { if (hcount < MAX_HANDLERS) handlers[hcount++] = h; } void emit(const char *msg) { for (int i = 0; i < hcount; i++) handlers[i](msg); } void on_log(const char *m) { printf("[LOG] %s\n", m); } void on_alert(const char *m) { printf("[ALERT] %s\n", m); } int main(void) { register_handler(on_log); register_handler(on_alert); emit("CPU temp high"); return 0; }
四、状态机:用函数指针表替代冗长的 switch
当状态很多时,switch-case 会变得臃肿。函数指针表让状态转移清晰可读。
state_machine.c
#include <stdio.h> typedef enum { ST_IDLE, ST_RUN, ST_PAUSE, ST_NUM } State; typedef enum { EV_START, EV_STOP, EV_PAUSE, EV_RESUME } Event; typedef State (*StateFunc)(Event); State idle_fn(Event e) { return (e == EV_START) ? ST_RUN : ST_IDLE; } State run_fn(Event e) { return (e == EV_PAUSE) ? ST_PAUSE : (e == EV_STOP ? ST_IDLE : ST_RUN); } State pause_fn(Event e) { return (e == EV_RESUME) ? ST_RUN : (e == EV_STOP ? ST_IDLE : ST_PAUSE); } static const StateFunc table[ST_NUM] = { idle_fn, run_fn, pause_fn }; int main(void) { State st = ST_IDLE; Event seq[] = { EV_START, EV_PAUSE, EV_RESUME, EV_STOP }; for (size_t i = 0; i < sizeof(seq)/sizeof(seq[0]); i++) { st = table[st](seq[i]); printf("state -> %d\n", st); } return 0; }
五、常见陷阱
- 类型不匹配: 把
void (*)(int)赋给void (*)(void)是未定义行为,编译器通常只给警告。 - NULL 解引用: 调用前务必检查函数指针非空,尤其是动态注册的回调。
- 生命周期问题: 别把局部函数的指针传出作用域(虽然函数本身在代码段,但语义上易混淆)。
六、总结
函数指针让 C 语言拥有了「运行时绑定」的能力,是实现回调、插件、状态机与策略模式的基础。掌握声明语法后,重点在于理解「框架调用户代码」的回调思想,以及用指针表替代分支的代码组织技巧。