第 11 章 · 函数指针

函数也有地址,存函数地址的指针就是函数指针——回调、策略、跳转表的基础。

10.1 声明与调用

int add(int a, int b) { return a + b; }

int (*f)(int, int);   // f 是「指向 (int,int)->int 函数的指针」
f = add;              // 函数名自动退化成函数指针(也可写 &add)
f = &add;             // 等价

int r = f(3, 4);      // 通过指针调用(也可写 (*f)(3, 4))
  • 声明从内向外读:f 先和 * 结合(是指针),再指向 (int, int) -> int 的函数。
  • 括号不能省int *f(int, int) 是「返回 int* 的函数」,不是函数指针。

10.2 typedef 简化

函数指针的裸声明很丑,typedef 让它可读:

typedef int (*BinOp)(int, int);   // BinOp 是一个类型:函数指针

BinOp f = add;                    // 清爽
int r = f(3, 4);

10.3 回调:把函数当参数传

函数指针作参数,让「策略」由调用者决定。标准库经典是 qsort

#include <stdlib.h>
int cmp_int(const void *a, const void *b) {
    int x = *(const int*)a, y = *(const int*)b;
    return (x > y) - (x < y);     // 返回负数/0/正数,注意避免 x-y 溢出
}

int arr[] = {3, 1, 4, 1, 5};
qsort(arr, 5, sizeof(int), cmp_int);

qsortvoid* 接收任意类型,用比较函数把「如何比较」交给调用者——这是泛型在 C 里的经典做法。

10.4 函数指针数组:跳转表(dispatch table)

把一组操作放进数组,用下标选择,比一长串 if/elseswitch 更紧凑、可扩展:

typedef int (*Op)(int, int);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

Op ops[] = { add, sub, mul };      // 函数指针数组
int r = ops[1](10, 3);             // 调 sub = 7

典型场景:菜单/命令分发、状态机转移表、语法分析器的运算符表。

10.5 返回函数指针的函数

函数可以返回函数指针,声明会变得诡异——用 typedef 拆解:

// 返回「指向 (int,int)->int 函数的指针」的函数,接受一个 int
typedef int (*BinOp)(int, int);
BinOp select(int which) {          // 清晰
    return which ? add : sub;
}

int r = select(1)(10, 3);          // 先选函数,再调用

10.6 与汇编 / 其他语言对照

  • 汇编:函数指针就是函数的入口地址,调用 f(3,4)call [f](间接调用)。间接调用会打断 CPU 的分支预测/内联,比直接调用略慢,这就是为什么 C++ 虚函数有开销。
  • Java:接口/匿名类/lambda 是函数指针的替代;JVM 底层用 invokeinterface/invokedynamic 做间接调用,概念一致。
  • C++std::function、函数对象、lambda、虚函数表都是函数指针的升级版;虚函数就是「结构体里的函数指针表」的语法糖(下一章会看到雏形)。
  • Python:函数是一等对象,func 直接就是「函数指针」,处处是回调,甚至不需要专门语法。

函数名自动退化成函数指针;typedef 拆声明;qsort=回调范式,函数指针数组=跳转表。