一、函数指针的核心概念
1. 什么是函数指针?
函数指针本质是一个指向函数入口地址的指针变量,可以通过它间接调用函数。
类似于数据指针保存的是变量的地址,函数指针保存的是函数的地址。
2. 为什么要用函数指针?
- 动态调用:在运行时决定调用哪个函数
- 实现回调(Callback):典型应用如事件处理、排序算法
- 解耦合:将函数调用与具体实现分离
二、函数指针基础语法
1. 函数指针的声明
基本格式:
<C>
返回类型 (*指针变量名)(参数类型列表);
示例1:指向无参数函数的指针
<C>
void (*func_ptr)(void); // 声明一个指向 void func() 的指针
示例2:带参数的指针
<C>
int (*compare)(int a, int b); // 指向 int compare(int, int) 的指针
2. 给函数指针赋值
<C>
// 假设已有函数定义int add(int a, int b) { return a + b; }int sub(int a, int b) { return a - b; }// 将add函数的地址赋给指针int (*operation)(int, int) = add;
3. 通过指针调用函数
<C>
int result = operation(3, 5); // 等价于调用 add(3,5)
三、4种典型应用场景
场景1:函数指针作为参数(回调函数)
// 定义回调类型
typedef void (*Callback)(int status);
void download_file(const char *url, Callback callback) {
// 模拟下载完成
printf("Downloading %s...\n", url);
callback(100); // 调用回调函数
}
// 实际回调函数
void on_download_finish(int progress) {
printf("Progress: %d%%\n", progress);
}
int main() {
download_file("test.zip", on_download_finish);
return 0;
}
场景2:在结构体中封装函数指针(模拟面向对象)
typedef struct {
void (*start)(void);
void (*stop)(void);
} DeviceDriver;
void led_start() { printf("LED ON\n"); }
void led_stop() { printf("LED OFF\n"); }
int main() {
DeviceDriver led = { led_start, led_stop };
led.start(); // 输出 "LED ON"
return 0;
}
场景3:实现策略模式
typedef int (*SortFunc)(int*, int); // 定义排序策略类型
// 两种不同的排序策略
int bubble_sort(int *arr, int len) { /* ... */ }
int quick_sort(int *arr, int len) { /* ... */ }
void sort_array(int *arr, int len, SortFunc strategy) {
strategy(arr, len); // 动态调用不同算法
}
int main() {
int data[5] = {3,1,4,5,2};
sort_array(data, 5, bubble_sort); // 使用冒泡排序
sort_array(data, 5, quick_sort); // 使用快速排序
return 0;
}
场景4:函数指针数组(状态机/菜单系统)
void menu_login() { printf("登录\n"); }
void menu_exit() { printf("退出\n"); }
void menu_help() { printf("帮助\n"); }
// 定义函数指针数组
void (*menu_items[])(void) = { menu_login, menu_exit, menu_help };
int main() {
int choice = 0;
while (1) {
printf("输入选项 (0-2): ");
scanf("%d", &choice);
if (choice >= 0 && choice <= 2) {
menu_items[choice](); // 通过索引调用函数
}
}
return 0;
}
四、深入理解:函数指针的内存模型
1. 验证函数地址的可访问性
printf("add函数地址: %p\n", (void*)add); // 输出类似 0x401550
printf("指针保存的地址: %p\n", (void*)operation); // 应与上方一致
2. 对比函数指针与普通指针
- 数据指针:指向数据的内存区域(可读写)
- 函数指针:指向代码段(通常只读)
五、常见问题与防御性编程
1. 如何避免空指针调用?
if (operation != NULL) {
operation(3, 5);
} else {
printf("Error: 函数指针未初始化!\n");
}
2. 类型不匹配的危险性
错误示例:
float (*wrong_ptr)(int) = (float (*)(int))add; // 强制转换危险!
wrong_ptr(10); // 可能引发段错误
六、实战项目建议
项目1:实现一个计算器
typedef double (*MathFunc)(double, double);
double add(double a, double b) { return a + b; }
double mul(double a, double b) { return a * b; }
MathFunc operations[] = {add, mul}; // 扩展更多运算...
int main() {
double result = operations[0](2.5, 3.5); // 执行加法
printf("Result: %.2f\n", result);
return 0;
}
项目2:实现异步任务调度器
typedef struct {
void (*task)(void*); // 任务函数指针
void *arg; // 参数指针
} AsyncTask;
void execute_task(AsyncTask *t) {
if (t && t->task) {
t->task(t->arg); // 执行任务并传递参数
}
}
// 示例任务:打印字符串
void print_task(void *arg) {
char *str = (char*)arg;
printf("Task Output: %s\n", str);
}
int main() {
AsyncTask task = { print_task, "Hello World!" };
execute_task(&task);
return 0;
}
七、重点注意事项
下一步学习建议:尝试实现一个基于函数指针的插件系统,实现动态加载不同功能模块。
-
严格匹配类型
- 返回类型和参数列表必须与指针声明完全一致
-
使用typedef简化复杂声明
-
typedef int (*Comparator)(const void*, const void*);
Comparator cmp = strcmp; // 用于qsort的比较函数 - 嵌入式系统中的特殊应用
在STM32中常用于中断向量表: - void (* const g_pfnVectors[])(void) = {
(void (*)(void))((unsigned long)_estack), // 栈顶地址
Reset_Handler, // 复位处理函数
NMI_Handler, // 其他中断处理程序...
}; - C++中的扩展
C++中可以使用std::function
和lambda表达式替代函数指针,但在嵌入式C中仍需掌握传统方法。 - 参考资料
- 书籍推荐:《C和指针》第13章
- GNU C手册:Function Pointers
- 在线练习:Godbolt编译器资源管理器(观察汇编代码)