函数也有地址,存函数地址的指针就是函数指针——回调、策略、跳转表的基础。
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* 的函数」,不是函数指针。函数指针的裸声明很丑,typedef 让它可读:
typedef int (*BinOp)(int, int); // BinOp 是一个类型:函数指针
BinOp f = add; // 清爽
int r = f(3, 4);
函数指针作参数,让「策略」由调用者决定。标准库经典是 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);
qsort用void*接收任意类型,用比较函数把「如何比较」交给调用者——这是泛型在 C 里的经典做法。
把一组操作放进数组,用下标选择,比一长串 if/else 或 switch 更紧凑、可扩展:
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
典型场景:菜单/命令分发、状态机转移表、语法分析器的运算符表。
函数可以返回函数指针,声明会变得诡异——用 typedef 拆解:
// 返回「指向 (int,int)->int 函数的指针」的函数,接受一个 int
typedef int (*BinOp)(int, int);
BinOp select(int which) { // 清晰
return which ? add : sub;
}
int r = select(1)(10, 3); // 先选函数,再调用
f(3,4) 是 call [f](间接调用)。间接调用会打断 CPU 的分支预测/内联,比直接调用略慢,这就是为什么 C++ 虚函数有开销。lambda 是函数指针的替代;JVM 底层用 invokeinterface/invokedynamic 做间接调用,概念一致。std::function、函数对象、lambda、虚函数表都是函数指针的升级版;虚函数就是「结构体里的函数指针表」的语法糖(下一章会看到雏形)。func 直接就是「函数指针」,处处是回调,甚至不需要专门语法。函数名自动退化成函数指针;
typedef拆声明;qsort=回调范式,函数指针数组=跳转表。