第9章:函数指针与回调函数:如何实现一个简单的计算器?
说实话,很多C语言初学者看到「函数指针」这四个字就头大。我当年也一样,觉得这东西不就是把函数地址存起来吗?有啥用?直到我在一个嵌入式项目里,需要根据不同的传感器类型调用不同的数据处理函数——如果不用函数指针,就得写一长串 if-else 或者 switch-case,代码又臭又长。
嗯,今天我们就用「实现一个简单计算器」这个经典例子,把函数指针和回调函数彻底讲明白。
9.1 什么是函数指针?
函数指针,说白了就是一个变量,它存的是函数的入口地址。你可以通过这个指针去调用函数,就像通过普通指针去访问变量一样。
定义方式有点绕,但记住这个模板就行:
// 定义一个函数指针类型
// 返回值类型 (*指针名)(参数列表)
int (*calc)(int, int);
这行代码的意思是:calc 是一个指针,它指向一个函数,这个函数接受两个 int 参数,返回一个 int。
typedef 给函数指针起个别名,这样写起来清爽很多。比如:
typedef int (*CalcFunc)(int, int);
CalcFunc p = add; // p 指向 add 函数
9.2 用函数指针实现计算器
我们先写四个基本运算函数:
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 div_op(int a, int b) {
if (b == 0) {
printf("错误:除数不能为0!\n");
return 0;
}
return a / b;
}
然后定义一个函数指针数组,把运算函数放进去:
int (*ops[4])(int, int) = {add, sub, mul, div_op};
char operators[] = {'+', '-', '*', '/'};
调用的时候,根据用户输入的操作符索引,直接通过指针调用:
int a = 10, b = 5;
int op_index = 2; // 对应乘法
int result = ops[op_index](a, b);
printf("%d %c %d = %d\n", a, operators[op_index], b, result);
你看,没有 if-else,没有 switch-case,代码干净利落。我在项目中用这种方式处理过几十种不同的协议解析函数,维护起来特别方便。
9.3 回调函数:让代码更灵活
回调函数,其实就是把函数指针当作参数传给另一个函数。那个函数在合适的时机「回调」你传进去的函数。
举个例子,我们写一个通用的计算函数:
int calculate(int a, int b, int (*func)(int, int)) {
return func(a, b);
}
// 使用时:
int result = calculate(10, 5, add); // 15
result = calculate(10, 5, mul); // 50
这个 calculate 函数根本不知道具体要做什么运算,它只负责调用你传进来的函数。这就是回调的核心思想——把「做什么」和「怎么做」分离开。
calculate 的代码,就能让它执行任意运算。这就是所谓的「开放封闭原则」——对扩展开放,对修改封闭。
9.4 完整计算器示例
下面是一个完整的控制台计算器程序,用函数指针和回调实现:
#include <stdio.h>
typedef int (*CalcFunc)(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; }
int div_op(int a, int b) {
if (b == 0) {
printf("除数不能为0!\n");
return 0;
}
return a / b;
}
// 回调函数:执行计算
int calculate(int a, int b, CalcFunc func) {
return func(a, b);
}
int main() {
CalcFunc funcs[] = {add, sub, mul, div_op};
char ops[] = {'+', '-', '*', '/'};
int a, b, choice;
printf("简单计算器\n");
printf("1: 加法 2: 减法 3: 乘法 4: 除法\n");
printf("请选择运算:");
scanf("%d", &choice);
printf("输入两个整数:");
scanf("%d %d", &a, &b);
if (choice < 1 || choice > 4) {
printf("无效选择\n");
return 1;
}
int result = calculate(a, b, funcs[choice - 1]);
printf("%d %c %d = %d\n", a, ops[choice - 1], b, result);
return 0;
}
9.5 函数指针的核心逻辑图
下面这张图展示了函数指针和回调函数在计算器中的工作流程:
9.6 避坑指南
函数指针虽然好用,但有几个坑我踩过,分享给你:
- 类型必须严格匹配:函数指针的返回值类型和参数类型必须和原函数完全一致,否则编译器会报错。我曾经因为参数类型写成了
float而实际函数用的是double,排查了半天。 - 空指针检查:调用函数指针之前,一定要检查它是否为 NULL。我在一个嵌入式项目中,因为某个回调函数没注册,直接调用导致系统崩溃。
- 作用域问题:函数指针指向的函数必须已经定义或声明,不能指向一个还没出现的函数。
9.7 什么时候用函数指针?
根据我的经验,以下几种场景特别适合用函数指针:
| 场景 | 说明 | 示例 |
|---|---|---|
| 策略模式 | 根据条件选择不同的算法 | 计算器、排序算法选择 |
| 回调机制 | 事件驱动、异步处理 | 定时器回调、按键中断 |
| 状态机 | 每个状态对应一个处理函数 | 通信协议解析 |
| 插件架构 | 动态加载功能模块 | 驱动层接口 |
说白了,函数指针就是C语言里实现「多态」的一种方式。虽然C没有面向对象的语法,但用函数指针一样能写出灵活、可扩展的代码。
嗯,这一章的内容就到这里。函数指针和回调函数是C语言进阶的重要一步,理解了它,你就能写出更优雅、更模块化的代码。下次写菜单程序或者协议解析的时候,不妨试试用函数指针来重构,你会爱上这种感觉的。
公众号:蓝海资料掘金营,微信 deep3321