目标:掌握 函数指针(function pointer) 的声明/赋值/调用、**回调(callback)**设计模式、表驱动(table-driven)与有限状态机(finite state machine, FSM) 的实战写法,以及类型不匹配与上下文传递的避坑。
1)基础语法速记
int add(int,int);
int (*fp)(int,int) = add; // 声明并赋值
int r = fp(3,4); // 调用
建议用 typedef 降低心智负担
typedef int (*binop_t)(int,int);
int sub(int a,int b){ return a-b; }
binop_t op = sub;
2)标准库里的回调案例:qsort
int cmp_int(const void *a,const void *b){
int x = *(const int*)a, y = *(const int*)b;
return (x>y) - (x<y);
}
qsort(arr, n, sizeof arr[0], cmp_int);
签名必须完全匹配。强转错误签名是 未定义行为。
3)回调 + 上下文(context)传递模式
很多 API 只给你一个 void* 存上下文。
typedef void (*on_event_t)(void *ctx, int code);
struct Handler {
void *ctx;
on_event_t on_event;
};
void dispatch(struct Handler *h, int code){
if (h->on_event) h->on_event(h->ctx, code);
}
好处:回调可无状态(把状态放 ctx),也可复用同一个处理函数配不同上下文。
4)表驱动:用函数指针数组消灭 switch-case
typedef int (*uop_t)(int);
static int op_inc(int x){ return x+1; }
static int op_dec(int x){ return x-1; }
static int op_neg(int x){ return -x; }
static uop_t OPS[] = { op_inc, op_dec, op_neg };
int apply(int which, int x){
if (which < 0 || which >= (int)(sizeof OPS/sizeof OPS[0])) return x;
return OPS[which](x);
}
扩展性:新增操作只需新增一个函数并放进表。
5)有限状态机(FSM)两种写法
A.表驱动 FSM(状态 × 事件 → 处理器)
typedef enum { S_INIT, S_OPEN, S_CLOSED, S_N } state_t;
typedef enum { E_OPEN, E_CLOSE, E_TIMEOUT, E_N } event_t;
typedef state_t (*handler_t)(void *ctx);
struct Cell { handler_t h; };
state_t on_init_open(void*), on_open_close(void*), on_any_timeout(void*);
static struct Cell T[S_N][E_N] = {
/* OPEN CLOSE TIMEOUT */
[S_INIT] ={ on_init_open, NULL, on_any_timeout },
[S_OPEN] ={ NULL, on_open_close, on_any_timeout },
[S_CLOSED]={ NULL, NULL, on_any_timeout },
};
state_t dispatch(void *ctx, state_t s, event_t e){
handler_t h = T[s][e].h;
return h ? h(ctx) : s;
}
优点:结构清晰,易于审计与单测。
B.状态函数式(状态函数返回下一个状态)
typedef state_t (*state_fn)(void *ctx, event_t e);
state_t st_init(void *ctx, event_t e){
switch(e){ case E_OPEN: /* ... */ return S_OPEN; default: return S_INIT; }
}
变体:用 state_fn 数组 state_fn STATES[S_N],每个状态有一个处理函数。
6)回调注册 API 的设计模板
typedef void (*cb_t)(void *ctx, int evt, const void *data);
struct Bus {
cb_t cb;
void *ctx;
};
void bus_subscribe(struct Bus *b, cb_t cb, void *ctx){
b->cb = cb; b->ctx = ctx;
}
void bus_emit(struct Bus *b, int evt, const void *data){
if (b->cb) b->cb(b->ctx, evt, data);
}
注意:API 里始终把上下文放第一参数,形成统一风格。
7)避坑与性能
- 签名必须匹配(参数与返回值类型);强转只是抹掉类型检查,不能修正不匹配。
- 不要把带 ... 可变参数的函数指针混用到固定参数签名。
- 热路径频繁小回调:可改为函数指针表 + static inline 包装,或直接开关宏避免间接跳转开销。
- 嵌入式/ISR:有的平台对函数指针调用有额外开销或限制,需基准测试。
- 与线程:回调里修改共享状态需加锁或用 _Atomic。
8)自检清单
- 会写 typedef 简化声明
- 会传 void* ctx 做上下文
- 会用函数指针数组实现表驱动
- FSM 能用“表”或“状态函数”两种模式落地
- 知道签名不匹配是 UB,绝不强转“糊弄”
9)迷你练习
1)把 qsort 的比较器改为“降序”。
2)为一个简易解析器设计状态表:S_NUM/S_OP/S_ERR × 事件 E_DIGIT/E_PLUS/E_OTHER。
3)把日志后端做成回调:可注册“写到文件”或“写到 socket”。
小结
- static/extern:管可见性与生命期;
- 宏:用全括号、do-while(0)、无副作用,该用 static inline 就用;
- 函数指针:用在回调、表驱动、状态机,签名要一丝不苟,上下文用 void* 传递。
