🧩 一、什么是函数指针?
- 函数指针是一个指向函数的指针变量,可以间接调用函数。
- C 中函数名本身就是一个函数地址,可以赋值给函数指针。
- 函数指针支持动态调用、回调机制、跳转表等高级功能。
🧱 二、函数指针的基本语法结构
✅ 函数指针的通用写法
返回类型 (*函数指针名)(参数类型1, 参数类型2, ...);
示例
int (*sumPtr)(int, int); // 返回 int,参数两个 int
float (*calc)(double, float, int); // 返回 float,三个参数
函数与指针例子
void sayHello(void *data) {
printf("Hello!\n");
}
void (*fp)(void *);
fp = sayHello;
fp(NULL); // 间接调用 sayHello
🧮 三、函数指针语法要点(括号的重要性)
void (*cb)(void *); // ✅ 正确,变量名是 cb
void (*)(void *) cb; // ❌ 错误,语法不合法
- 括号用于明确
*cb
是函数指针而不是函数返回值。
✂️ 四、使用 typedef 简化函数指针声明
写法:
typedef void (*Callback)(void *);
Callback cb = sayHello;
cb(NULL);
✅ 优点:
- 更清晰
- 更易维护
- 重复使用方便
🚫 五、不推荐使用的宏方式(#define)
#define Callback (void (*)(void *))
Callback cb; // ✅ 语法上可行,但有陷阱
❌ 不推荐的原因:
- 宏替换没有类型检查
- 括号嵌套复杂,容易出错
- 可读性差,不如
typedef
🧠 六、函数指针的实际应用场景
1. 回调函数机制
#include <stdio.h>
void onEvent(void *data) {
const char *msg = (const char *)data;
printf("Event triggered: %s\n", msg);
}
void registerCallback(void (*cb)(void *), void *param) {
printf("Registering callback...\n");
cb(param);
}
int main() {
const char *message = "Hello from main!";
registerCallback(onEvent, (void *)message);
return 0;
}
🔹 输出:
Registering callback...
Event triggered: Hello from main!
2. 函数表 / 跳转表
typedef void (*Handler)(void *);
void func1(void *p) { printf("Func1\n"); }
void func2(void *p) { printf("Func2\n"); }
Handler table[2] = {func1, func2};
table[0](NULL); // 调用 func1
🧨 七、函数指针常见语法陷阱
错误写法 | 问题描述 |
---|---|
void (*)(void *) cb; | ❌ 编译器不知道 cb 是变量名 |
#define Callback void(*)(void *) | ❌ 宏展开顺序混乱、类型不明确 |
int* a, b; | ❌ 只有 a 是指针,b 是普通 int |
🏗️ 八、函数返回结构体 + 多参数的函数指针写法
示例函数:
typedef struct {
int x;
float y;
} MyStruct;
MyStruct makeData(int a, double b);
函数指针写法:
MyStruct (*fp)(int, double);
typedef MyStruct (*DataMaker)(int, double);
使用示例:
DataMaker funcPtr = makeData;
MyStruct result = funcPtr(10, 3.14);
🎯 九、为什么不是 int*
和 double*
?
因为你定义的函数是值传递:
MyStruct makeData(int a, double b);
所以函数指针也必须写成:
MyStruct (*fp)(int, double); // ✅ 正确
❌ 如果写成:
MyStruct (*fp)(int*, double*); // 错误!参数类型不一致
会导致无法赋值函数指针,编译器会报错。
⚖️ 十、传值 vs 传指针 对比
类型 | 说明 | 示例 |
---|---|---|
int | 值传递,复制一份 | makeData(5, 3.14) |
int* | 指针传递,传地址 | makeData(&a, &b) |
🧵 十一、结构体作为参数或返回值
函数指针也可以:
typedef struct {
int a;
int b;
} Pair;
typedef Pair (*PairFunc)(Pair, Pair); // 两个结构体参数,返回结构体
typedef void (*PairHandler)(Pair *); // 结构体指针参数
🔧 十二、实战建议与最佳实践
场景 | 建议写法 |
---|---|
结构体很大 | 使用结构体指针 |
函数参数类型复杂 | 使用 typedef 简化函数指针 |
多个函数统一接口调用 | 函数指针数组 |
接口回调(如状态机/事件系统) | 用函数指针实现解耦 |
🧩 十三、快速对照表
函数定义 | 函数指针类型 |
---|---|
int add(int a, int b); | int (*fp)(int, int); |
void show(float x); | void (*fp)(float); |
MyStruct build(char*, int); | MyStruct (*fp)(char*, int); |
void update(MyStruct *p); | void (*fp)(MyStruct *); |
✅ 总结回顾
知识点 | 要点说明 |
---|---|
函数指针语法 | 返回类型 (*指针变量)(参数类型1, 参数类型2...) |
括号作用 | 必须用括号将 *指针名 包裹起来,区分函数声明和指针声明 |
参数类型匹配 | 函数指针类型必须与实际函数签名一致 |
typedef 简化 | 使用 typedef 更优雅地声明复杂函数指针类型 |
传值 / 传指针区别 | 是否复制实参 or 传递地址,对应类型不同 |
实用技巧 | 用于回调、注册器、接口抽象、状态机、跳转表等高阶场景 |