前置知识: C

函数指针回调与跳转表

00:00
5 min Advanced 2026/6/14

C语言函数指针回调机制与跳转表实现。

概述

函数指针是C语言中最灵活的特性之一,它将函数的地址作为数据存储,使得函数可以像普通变量一样被传递、存储和调用。回调函数是函数指针的典型应用,允许调用者将自定义行为注入被调用者的执行流程中。跳转表则利用函数指针数组替代冗长的 switch-case 分支结构,实现 O(1) 的分发查找。三者共同构成了C语言中实现多态、事件驱动和策略模式的基础设施。

基础概念

函数指针的声明与初始化

函数指针是一个指向函数的指针变量,其类型由返回值和参数列表共同决定。声明语法为 返回类型 (*指针名)(参数列表)

/* 声明一个指向 int(int,int) 类型函数的指针 */
int (*fp)(int, int);

/* 定义一个匹配的函数 */
int add(int a, int b) { return a + b; }

/* 用函数名初始化,函数名即地址 */
fp = add;       /* 推荐写法 */
fp = &add;      /* 等价写法,& 可省略 */

函数指针的调用

通过函数指针调用函数有两种等价写法:

int result1 = fp(3, 4);      /* 直接调用,推荐 */
int result2 = (*fp)(3, 4);   /* 显式解引用调用 */
/* 两种写法结果相同,均为 7 */

typedef 简化函数指针类型

函数指针的声明语法较为冗长,使用 typedef 可以显著提高可读性:

/* 定义函数指针类型 */
typedef int (*BinaryOp)(int, int);

/* 用类型名声明变量,清晰简洁 */
BinaryOp op = add;
op(10, 20);  /* 30 */

回调函数的本质

回调函数是通过函数指针传递给另一个函数的函数,被调用者在特定时机回过头来调用它。其本质是控制反转:调用者不再决定做什么,而是将”做什么”的决定权交给外部。

/* 比较函数的回调原型 */
typedef int (*CompareFunc)(const void *, const void *);

/* qsort 使用回调来决定排序规则 */
void qsort(void *base, size_t nmemb, size_t size, CompareFunc compar);

跳转表的本质

跳转表是函数指针的数组,用索引直接定位并调用目标函数,避免了 switch-case 的逐条比较。它本质上是将分支逻辑转化为数据查找。

/* 跳转表:用数组索引替代 switch-case */
double (*ops[])(double, double) = { add, sub, mul, divide };
/* ops[0] 即 add,ops[1] 即 sub,依此类推 */

快速上手

第一个函数指针程序

#include <stdio.h>

/* 定义两个简单函数 */
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

int main(void) {
    /* 声明函数指针并指向 add */
    int (*op)(int, int) = add;
    printf("3 + 4 = %d\n", op(3, 4));  /* 7 */

    /* 切换指向 multiply */
    op = multiply;
    printf("3 * 4 = %d\n", op(3, 4));  /* 12 */

    return 0;
}

第一个回调函数程序

#include <stdio.h>

/* 回调函数类型 */
typedef void (*Callback)(int result);

/* 模拟异步操作,完成后调用回调 */
void asyncOperation(int input, Callback cb) {
    int result = input * 2;
    cb(result);  /* 操作完成,回调通知 */
}

/* 用户定义的回调处理函数 */
void onResult(int result) {
    printf("异步操作结果: %d\n", result);
}

int main(void) {
    asyncOperation(21, onResult);  /* 输出: 异步操作结果: 42 */
    return 0;
}

第一个跳转表程序

#include <stdio.h>

double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }

/* 跳转表 */
double (*ops[])(double, double) = { add, sub, mul, divide };

/* 通过索引调用 */
double calculate(int op, double a, double b) {
    if (op >= 0 && op < 4) return ops[op](a, b);
    return 0;
}

int main(void) {
    printf("10 + 3 = %.1f\n", calculate(0, 10, 3));  /* 13.0 */
    printf("10 - 3 = %.1f\n", calculate(1, 10, 3));  /* 7.0  */
    printf("10 * 3 = %.1f\n", calculate(2, 10, 3));  /* 30.0 */
    printf("10 / 3 = %.1f\n", calculate(3, 10, 3));  /* 3.3  */
    return 0;
}

详细用法

函数指针数组

当需要管理一组同签名的函数时,函数指针数组比散列的 switch-case 更清晰:

#include <stdio.h>

/* 四则运算函数 */
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }

/* 函数指针数组 + 名称数组,配合使用 */
double (*ops[])(double, double) = { add, sub, mul, divide };
const char *op_names[] = { "加", "减", "乘", "除" };

int main(void) {
    double a = 20.0, b = 4.0;
    for (int i = 0; i < 4; i++) {
        printf("%.0f %s %.0f = %.2f\n", a, op_names[i], b, ops[i](a, b));
    }
    return 0;
}

返回函数指针的函数

函数可以作为工厂,根据条件返回不同的函数指针:

#include <stdio.h>

typedef double (*MathOp)(double, double);

double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }

/* 根据运算符返回对应的函数指针 */
MathOp getOperator(char op) {
    switch (op) {
        case '+': return add;
        case '-': return sub;
        case '*': return mul;
        default:  return NULL;  /* 未知运算符 */
    }
}

int main(void) {
    MathOp op = getOperator('+');
    if (op) {
        printf("5 + 3 = %.0f\n", op(5, 3));  /* 8 */
    }
    return 0;
}

带上下文的回调

在实际项目中,回调通常需要额外的上下文信息。C语言通过 void 指针传递上下文:

#include <stdio.h>
#include <stdlib.h>

/* 回调类型:接收元素值和用户上下文 */
typedef void (*ForEachCallback)(int element, void *context);

/* 遍历数组并对每个元素调用回调 */
void forEach(int *arr, int len, ForEachCallback cb, void *context) {
    for (int i = 0; i < len; i++) {
        cb(arr[i], context);
    }
}

/* 回调:求和,上下文为累加器 */
void sumCallback(int element, void *context) {
    int *sum = (int *)context;
    *sum += element;
}

/* 回调:过滤并打印偶数,上下文为计数器 */
void evenCallback(int element, void *context) {
    int *count = (int *)context;
    if (element % 2 == 0) {
        printf("偶数: %d\n", element);
        (*count)++;
    }
}

int main(void) {
    int arr[] = { 1, 2, 3, 4, 5, 6 };
    int sum = 0;
    forEach(arr, 6, sumCallback, &sum);
    printf("总和: %d\n", sum);  /* 21 */

    int evenCount = 0;
    forEach(arr, 6, evenCallback, &evenCount);
    printf("偶数个数: %d\n", evenCount);  /* 3 */

    return 0;
}

使用 qsort 的比较回调

标准库的 qsort 函数是回调的经典应用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[32];
    int score;
} Student;

/* 按分数降序排列 */
int compareByScoreDesc(const void *a, const void *b) {
    const Student *sa = (const Student *)a;
    const Student *sb = (const Student *)b;
    return sb->score - sa->score;  /* 降序 */
}

/* 按姓名字典序排列 */
int compareByName(const void *a, const void *b) {
    const Student *sa = (const Student *)a;
    const Student *sb = (const Student *)b;
    return strcmp(sa->name, sb->name);
}

int main(void) {
    Student students[] = {
        { "张三", 85 },
        { "李四", 92 },
        { "王五", 78 },
    };
    int n = sizeof(students) / sizeof(students[0]);

    /* 按分数排序 */
    qsort(students, n, sizeof(Student), compareByScoreDesc);
    printf("按分数降序:\n");
    for (int i = 0; i < n; i++) {
        printf("  %s: %d\n", students[i].name, students[i].score);
    }

    /* 按姓名排序 */
    qsort(students, n, sizeof(Student), compareByName);
    printf("按姓名排序:\n");
    for (int i = 0; i < n; i++) {
        printf("  %s: %d\n", students[i].name, students[i].score);
    }

    return 0;
}

跳转表实现命令分发

跳转表在命令解析和协议处理中非常实用:

#include <stdio.h>
#include <string.h>

/* 命令处理函数类型 */
typedef void (*CmdHandler)(const char *arg);

/* 各命令的处理函数 */
void cmdHelp(const char *arg) {
    printf("可用命令: help, list, add, remove, quit\n");
}

void cmdList(const char *arg) {
    printf("显示列表内容...\n");
}

void cmdAdd(const char *arg) {
    printf("添加: %s\n", arg ? arg : "(无参数)");
}

void cmdRemove(const char *arg) {
    printf("移除: %s\n", arg ? arg : "(无参数)");
}

void cmdQuit(const char *arg) {
    printf("退出程序\n");
}

/* 命令表项 */
typedef struct {
    const char *name;    /* 命令名 */
    CmdHandler handler;  /* 处理函数 */
} CmdEntry;

/* 命令跳转表 */
CmdEntry cmdTable[] = {
    { "help",   cmdHelp   },
    { "list",   cmdList   },
    { "add",    cmdAdd    },
    { "remove", cmdRemove },
    { "quit",   cmdQuit   },
};

#define CMD_COUNT (sizeof(cmdTable) / sizeof(cmdTable[0]))

/* 分发命令 */
void dispatch(const char *cmd, const char *arg) {
    for (int i = 0; i < (int)CMD_COUNT; i++) {
        if (strcmp(cmdTable[i].name, cmd) == 0) {
            cmdTable[i].handler(arg);
            return;
        }
    }
    printf("未知命令: %s\n", cmd);
}

int main(void) {
    dispatch("help", NULL);       /* 显示帮助 */
    dispatch("add", "item1");     /* 添加 item1 */
    dispatch("list", NULL);       /* 显示列表 */
    dispatch("unknown", NULL);    /* 未知命令 */
    return 0;
}

常见场景

事件驱动系统

图形界面和网络框架广泛使用回调来处理事件:

#include <stdio.h>

/* 事件类型 */
typedef enum { EVENT_CLICK, EVENT_KEY, EVENT_TIMER } EventType;

/* 事件数据 */
typedef struct {
    EventType type;
    int x, y;       /* 坐标(点击事件) */
    int keycode;    /* 键码(键盘事件) */
} Event;

/* 事件回调类型 */
typedef void (*EventHandler)(const Event *e);

/* 事件处理器注册表 */
EventHandler handlers[3] = { NULL };

/* 注册事件处理器 */
void registerHandler(EventType type, EventHandler handler) {
    if (type >= 0 && type < 3) {
        handlers[type] = handler;
    }
}

/* 触发事件 */
void fireEvent(const Event *e) {
    if (e->type >= 0 && e->type < 3 && handlers[e->type]) {
        handlers[e->type](e);
    }
}

/* 具体的事件处理函数 */
void onClick(const Event *e) {
    printf("鼠标点击: (%d, %d)\n", e->x, e->y);
}

void onKey(const Event *e) {
    printf("按键: %d\n", e->keycode);
}

int main(void) {
    /* 注册处理器 */
    registerHandler(EVENT_CLICK, onClick);
    registerHandler(EVENT_KEY, onKey);

    /* 模拟事件 */
    Event click = { EVENT_CLICK, 100, 200, 0 };
    Event key   = { EVENT_KEY, 0, 0, 65 };
    fireEvent(&click);  /* 鼠标点击: (100, 200) */
    fireEvent(&key);    /* 按键: 65 */

    return 0;
}

策略模式

通过函数指针实现运行时切换算法策略:

#include <stdio.h>

/* 排序策略类型 */
typedef void (*SortStrategy)(int *arr, int n);

/* 冒泡排序 */
void bubbleSort(int *arr, int n) {
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - 1 - i; j++)
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
}

/* 选择排序 */
void selectionSort(int *arr, int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i;
        for (int j = i + 1; j < n; j++)
            if (arr[j] < arr[min]) min = j;
        if (min != i) {
            int tmp = arr[i];
            arr[i] = arr[min];
            arr[min] = tmp;
        }
    }
}

/* 使用策略排序 */
void sortWith(int *arr, int n, SortStrategy strategy) {
    strategy(arr, n);
}

void printArr(int *arr, int n) {
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");
}

int main(void) {
    int a[] = { 5, 3, 1, 4, 2 };
    int b[] = { 5, 3, 1, 4, 2 };

    /* 使用冒泡排序策略 */
    sortWith(a, 5, bubbleSort);
    printf("冒泡排序: "); printArr(a, 5);

    /* 使用选择排序策略 */
    sortWith(b, 5, selectionSort);
    printf("选择排序: "); printArr(b, 5);

    return 0;
}

插件架构

动态加载库中的函数指针实现插件机制:

#include <stdio.h>
#include <dlfcn.h>

/* 插件接口定义 */
typedef const char *(*PluginGetNameFunc)(void);
typedef void (*PluginRunFunc)(const char *arg);

int main(void) {
    /* 动态加载插件库 */
    void *handle = dlopen("./plugin.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "加载失败: %s\n", dlerror());
        return 1;
    }

    /* 获取插件函数 */
    PluginGetNameFunc getName = (PluginGetNameFunc)dlsym(handle, "pluginGetName");
    PluginRunFunc run = (PluginRunFunc)dlsym(handle, "pluginRun");

    if (getName && run) {
        printf("插件: %s\n", getName());
        run("hello");
    }

    dlclose(handle);
    return 0;
}

注意事项

函数指针的类型安全

函数指针的类型必须与目标函数的签名完全匹配,包括返回值类型和所有参数类型。不匹配的转换会导致未定义行为:

int add(int a, int b) { return a + b; }
double (*fp)(double, double) = (double (*)(double, double))add;  /* 危险! */
/* 调用 fp 会产生未定义行为,因为 int 和 double 的传参方式不同 */

空函数指针检查

调用函数指针会导致程序崩溃,调用前必须检查

typedef void (*Callback)(void);

void safeCall(Callback cb) {
    if (cb != NULL) {  /* 必须检查 */
        cb();
    }
}

回调中的重入问题

回调函数可能在中断递归场景中被重复调用,需要注意状态一致性

/* 不安全的回调:全局状态被回调修改 */
static int counter = 0;

void unsafeCallback(void) {
    counter++;
    /* 如果此处被中断并再次调用,counter 可能不一致 */
}

/* 安全做法:使用局部变量或原子操作 */

跳转表越界访问

通过索引访问,必须进边界检查,否则可能调用到非法地址:

double (*ops[])(double, double) = { add, sub, mul, divide };
#define OPS_COUNT (sizeof(ops) / sizeof(ops[0]))

double safeCalculate(int op, double a, double b) {
    if (op < 0 || op >= (int)OPS_COUNT) {
        fprintf(stderr, "无效操作码: %d\n", op);
        return 0;
    }
    return ops[op](a, b);
}

函数指针与 volatile

中断服务程序信号处理函数中使用的函数指针声明为 volatile,防止编译器优化掉看似”余”的读取

/* 中断中可能被修改的回调 */
volatile typedef void (*IsrCallback)(void);
volatile IsrCallback isrHandler = NULL;

平台相关的调用约定

不同平台编译器可能有不同的调用约定(cdecl、stdcall、fastcall 等),跨平台代码需要明确指定:

/* Windows API 回调使用 __stdcall 约定 */
#ifdef _WIN32
typedef void (__stdcall *WinCallback)(int);
#else
typedef void (*WinCallback)(int);
#endif

进阶用法

用函数指针模拟面向对象多态

C语言没有虚函数,但可以通过结构体 + 函数指针实现类似多态的效果

#include <stdio.h>
#include <stdlib.h>

/* "基类":形状接口 */
typedef struct Shape Shape;

struct Shape {
    void (*draw)(const Shape *self);
    double (*area)(const Shape *self);
    void (*destroy)(Shape *self);
};

/* 圆形 */
typedef struct {
    Shape base;     /* 必须放在首位,保证指针可互换 */
    double radius;
} Circle;

void circleDraw(const Shape *self) {
    const Circle *c = (const Circle *)self;
    printf("绘制圆形,半径: %.1f\n", c->radius);
}

double circleArea(const Shape *self) {
    const Circle *c = (const Circle *)self;
    return 3.14159 * c->radius * c->radius;
}

void circleDestroy(Shape *self) {
    free(self);
}

Shape *createCircle(double radius) {
    Circle *c = (Circle *)malloc(sizeof(Circle));
    c->base.draw = circleDraw;
    c->base.area = circleArea;
    c->base.destroy = circleDestroy;
    c->radius = radius;
    return (Shape *)c;
}

/* 矩形 */
typedef struct {
    Shape base;
    double width, height;
} Rectangle;

void rectDraw(const Shape *self) {
    const Rectangle *r = (const Rectangle *)self;
    printf("绘制矩形,宽: %.1f 高: %.1f\n", r->width, r->height);
}

double rectArea(const Shape *self) {
    const Rectangle *r = (const Rectangle *)self;
    return r->width * r->height;
}

void rectDestroy(Shape *self) {
    free(self);
}

Shape *createRectangle(double w, double h) {
    Rectangle *r = (Rectangle *)malloc(sizeof(Rectangle));
    r->base.draw = rectDraw;
    r->base.area = rectArea;
    r->base.destroy = rectDestroy;
    r->width = w;
    r->height = h;
    return (Shape *)r;
}

/* 多态调用 */
void printShapeInfo(Shape *s) {
    s->draw(s);
    printf("面积: %.2f\n", s->area(s));
}

int main(void) {
    Shape *shapes[2];
    shapes[0] = createCircle(5.0);
    shapes[1] = createRectangle(4.0, 6.0);

    for (int i = 0; i < 2; i++) {
        printShapeInfo(shapes[i]);
    }

    for (int i = 0; i < 2; i++) {
        shapes[i]->destroy(shapes[i]);
    }

    return 0;
}

观察者模式

使用回调实现多的通知机制:

#include <stdio.h>

#define MAX_OBSERVERS 10

typedef void (*Observer)(const char *event, void *data);

/* 主题:管理观察者列表 */
typedef struct {
    Observer observers[MAX_OBSERVERS];
    int count;
} Subject;

void subjectInit(Subject *s) {
    s->count = 0;
}

void subjectAttach(Subject *s, Observer obs) {
    if (s->count < MAX_OBSERVERS) {
        s->observers[s->count++] = obs;
    }
}

void subjectNotify(Subject *s, const char *event, void *data) {
    for (int i = 0; i < s->count; i++) {
        s->observers[i](event, data);
    }
}

/* 具体观察者 */
void loggerObserver(const char *event, void *data) {
    printf("[日志] 事件: %s\n", event);
}

void alertObserver(const char *event, void *data) {
    printf("[警报] 收到通知: %s\n", event);
}

int main(void) {
    Subject subject;
    subjectInit(&subject);

    /* 注册观察者 */
    subjectAttach(&subject, loggerObserver);
    subjectAttach(&subject, alertObserver);

    /* 触发事件,所有观察者收到通知 */
    subjectNotify(&subject, "温度过高", NULL);

    return 0;
}

基于跳转表的状态机

可以实现有限状态机,每个状态对应一个处理函数

#include <stdio.h>

/* 状态枚举 */
typedef enum { STATE_IDLE, STATE_RUNNING, STATE_PAUSED, STATE_COUNT } State;

/* 事件枚举 */
typedef enum { EVT_START, EVT_PAUSE, EVT_RESUME, EVT_STOP } Event;

/* 状态处理函数类型 */
typedef State (*StateHandler)(Event evt);

/* 各状态的处理逻辑 */
State handleIdle(Event evt) {
    if (evt == EVT_START) {
        printf("启动...\n");
        return STATE_RUNNING;
    }
    printf("空闲状态忽略事件 %d\n", evt);
    return STATE_IDLE;
}

State handleRunning(Event evt) {
    switch (evt) {
        case EVT_PAUSE:
            printf("暂停...\n");
            return STATE_PAUSED;
        case EVT_STOP:
            printf("停止...\n");
            return STATE_IDLE;
        default:
            printf("运行状态忽略事件 %d\n", evt);
            return STATE_RUNNING;
    }
}

State handlePaused(Event evt) {
    switch (evt) {
        case EVT_RESUME:
            printf("恢复...\n");
            return STATE_RUNNING;
        case EVT_STOP:
            printf("停止...\n");
            return STATE_IDLE;
        default:
            printf("暂停状态忽略事件 %d\n", evt);
            return STATE_PAUSED;
    }
}

/* 状态跳转表 */
StateHandler stateTable[STATE_COUNT] = {
    handleIdle,     /* STATE_IDLE    */
    handleRunning,  /* STATE_RUNNING */
    handlePaused,   /* STATE_PAUSED  */
};

int main(void) {
    State current = STATE_IDLE;

    /* 模拟事件序列 */
    Event events[] = { EVT_START, EVT_PAUSE, EVT_RESUME, EVT_STOP };
    int n = sizeof(events) / sizeof(events[0]);

    for (int i = 0; i < n; i++) {
        current = stateTable[current](events[i]);
    }

    return 0;
}

C11 泛型结合函数指针

C11 的 _Generic 可以与函数指针配合,实现类型安全泛型分发:

#include <stdio.h>

/* 各类型的打印函数 */
void printInt(void *p)    { printf("int: %d\n", *(int *)p); }
void printDouble(void *p) { printf("double: %.2f\n", *(double *)p); }
void printChar(void *p)   { printf("char: %c\n", *(char *)p); }

/* 根据类型选择打印函数 */
#define PRINT(x) _Generic((x), \
    int:    printInt,           \
    double: printDouble,        \
    char:   printChar           \
)(&(x))

int main(void) {
    int a = 42;
    double b = 3.14;
    char c = 'X';

    PRINT(a);  /* int: 42 */
    PRINT(b);  /* double: 3.14 */
    PRINT(c);  /* char: X */

    return 0;
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式