4. 函数指针数组:让代码拥有“选择能力”
好,咱们接着聊函数指针。前面我们学了单个函数指针怎么用,但实际项目中,你往往需要管理一组功能相似的函数。这时候,函数指针数组就派上用场了。
说白了,函数指针数组就是一个数组,数组里每个元素都是一个函数指针。它让程序能根据条件,动态选择执行哪个函数。这种能力在状态机、菜单系统里特别常见。
4.1 函数指针数组的定义与使用
先看定义语法。假设我们有三个数学运算函数:
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; }
定义一个函数指针数组,指向这些函数:
int (*op_func[])(int, int) = {add, sub, mul};
嗯,这里要注意:op_func 是一个数组,每个元素都是 int (*)(int, int) 类型的函数指针。调用时,直接用下标索引:
int result = op_func[1](10, 5); // 调用 sub(10, 5),结果是 5
我个人习惯用枚举来定义索引,这样代码可读性更好:
typedef enum {
OP_ADD,
OP_SUB,
OP_MUL,
OP_MAX
} op_type_t;
int (*op_func[OP_MAX])(int, int) = {add, sub, mul};
// 使用时
op_type_t cmd = OP_ADD;
int result = op_func[cmd](a, b);
4.2 实现状态机
状态机是嵌入式开发的老朋友了。传统做法是用 switch-case,但函数多了,代码会变得又臭又长。用函数指针数组,代码会清爽很多。
我曾经在一个通信协议栈项目里,用函数指针数组管理了 20 多个状态。每个状态对应一个处理函数,状态切换就是改变数组下标。来看个简化例子:
// 状态处理函数
void state_idle(void) { /* 空闲状态处理 */ }
void state_rx(void) { /* 接收状态处理 */ }
void state_tx(void) { /* 发送状态处理 */ }
void state_error(void) { /* 错误状态处理 */ }
// 状态枚举
typedef enum {
ST_IDLE,
ST_RX,
ST_TX,
ST_ERROR,
ST_MAX
} state_t;
// 函数指针数组
void (*state_table[ST_MAX])(void) = {
state_idle,
state_rx,
state_tx,
state_error
};
// 状态机主循环
state_t current_state = ST_IDLE;
while(1) {
state_table[current_state](); // 执行当前状态处理函数
// 根据事件更新 current_state
}
你看,主循环就一行代码。新增状态时,只需要写新的处理函数,然后在枚举和数组里各加一项。不会像 switch-case 那样,改一个地方要检查所有 case。
4.3 实现菜单驱动系统
菜单系统是函数指针数组的另一个经典应用。想想看,一个嵌入式设备的菜单,每个菜单项对应一个操作。用函数指针数组,可以轻松实现菜单的嵌套和跳转。
我做过一个手持设备的菜单系统,有 3 级菜单,每级菜单都是一个函数指针数组。结构大概是这样:
// 菜单项处理函数
void menu_main(void) { /* 主菜单显示 */ }
void menu_setting(void) { /* 设置菜单显示 */ }
void menu_about(void) { /* 关于菜单显示 */ }
void menu_exit(void) { /* 退出菜单 */ }
// 菜单项结构
typedef struct {
const char *name; // 菜单名称
void (*handler)(void); // 处理函数
int sub_menu; // 子菜单索引,-1 表示无子菜单
} menu_item_t;
// 主菜单
menu_item_t main_menu[] = {
{"1. 设置", menu_setting, 1},
{"2. 关于", menu_about, -1},
{"3. 退出", menu_exit, -1},
{NULL, NULL, -1} // 结束标记
};
// 设置子菜单
menu_item_t setting_menu[] = {
{"1. 亮度", menu_brightness, -1},
{"2. 音量", menu_volume, -1},
{"3. 返回", menu_main, 0},
{NULL, NULL, -1}
};
菜单导航时,根据用户输入,调用对应的处理函数,同时可以切换到子菜单。核心逻辑就几句话:
void menu_navigate(menu_item_t *menu, int selection) {
if (menu[selection].handler) {
menu[selection].handler(); // 执行菜单功能
if (menu[selection].sub_menu >= 0) {
// 切换到子菜单
current_menu = sub_menus[menu[selection].sub_menu];
}
}
}
4.4 知识体系图
下面这张图,帮你理清函数指针数组的核心脉络:
函数指针数组,说白了就是给代码装了个「选择器」。你想想看,没有它的时候,你要写一堆 if-else 或 switch-case。有了它,一行代码就能搞定选择和执行。代码量少了,可读性高了,维护也方便了。
嗯,这一节就到这里。记住:函数指针数组不是炫技,它是解决实际问题的工具。下次写状态机或菜单系统时,试试这个思路,你会爱上它的简洁。