一、函数指针的基本语法

函数指针的声明容易让人困惑,核心规则是:把函数声明中的函数名替换为 (*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 语言拥有了「运行时绑定」的能力,是实现回调、插件、状态机与策略模式的基础。掌握声明语法后,重点在于理解「框架调用户代码」的回调思想,以及用指针表替代分支的代码组织技巧。