嵌入式 C 语言入门:函数指针基础笔记 —— 从计算器优化到指针本质
前言
大家好,这里是 Hello_Embed。在之前的笔记中,我们学习了函数的基本用法和多文件编程,而函数指针是 C 语言中更灵活的特性 —— 它能让函数像变量一样被 “传递” 和 “赋值”,尤其在需要动态选择函数(比如根据用户输入切换不同运算)时非常实用。本文通过一个计算器的例子,从代码优化入手,讲解函数指针的概念、用法,以及如何用typedef
简化函数指针的定义,帮你理解这一嵌入式开发中常用的高级特性。
🧮 从计算器代码说起:重复逻辑的痛点
我们先在vscode上实现一个简单的计算器,支持加减乘除运算,代码如下:
// main.c
#include <stdio.h>// 定义加减乘除函数
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int multi(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }int main(void)
{int v1 = 4, v2 = 2;char c;printf("enter + - * /\n");scanf("%c", &c); // 接收用户输入的运算符// 根据运算符选择对应函数switch(c){case '+':printf("%d\n", add(v1, v2));break;case '-':printf("%d\n", sub(v1, v2));break;case '*':printf("%d\n", multi(v1, v2));break;case '/':printf("%d\n", div(v1, v2));break;default:printf("error\n");}return 0;
}
运行代码,输出正常:
但存在一个问题:如果需要用同一运算符计算多组数据(比如先算4+2
,再算3+1
),就得重复写switch
判断,代码冗余且不灵活。
🔗 用函数指针优化:让函数 “可赋值”
解决上述问题的关键是函数指针—— 一种专门指向函数地址的指针变量。先看优化后的代码:
int main(void)
{char c;// 定义函数指针f:指向“参数为两个int、返回值为int”的函数int (*f)(int a, int b); printf("enter + - * /\n");scanf("%c", &c);// 根据运算符给函数指针赋值(指向对应函数)switch(c){case '+': f = add; break;case '-': f = sub; break;case '*': f = multi; break;case '/': f = div; break;default: printf("error\n"); return 0;}// 用同一函数指针计算多组数据int v1 = 4, v2 = 2;int result1 = f(v1, v2); // 等价于调用add/sub/...printf("result1 = %d\n", result1);v1 = 3, v2 = 1;int result2 = f(v1, v2);printf("result2 = %d\n", result2);return 0;
}
只需一次switch
判断,就能用函数指针f
重复调用对应函数,解决了代码冗余问题:
🤔 函数指针的本质:函数即地址
为什么函数指针能直接 “赋值” 和 “调用”?这涉及一个冷知识:调用函数时,实际使用的是它的地址。
函数的地址特性
函数被编译后存储在程序的代码段(如单片机的 Flash),每个函数都有唯一的起始地址。我们可以通过打印函数名验证这一点:
// 打印add和sub函数的地址
printf("add = 0x%x\n", add);
printf("sub = 0x%x\n", sub);
运行结果如图所示,函数名直接代表它的地址(图中为 6 位地址,因 VSCode 环境与 STM32 硬件不同,但本质是地址):
函数指针的定义与用法
函数指针的作用是 “存储函数的地址”,定义格式为:
返回值类型 (*指针变量名)(参数类型1, 参数类型2, ...);
以计算器为例,add
等函数的返回值为int
,参数为两个int
,因此对应的函数指针定义为:
int (*f)(int a, int b); // f是函数指针,可指向add/sub等函数
打印函数地址的三种方式
由于函数名即地址,我们可以通过以下方式获取函数地址:
- 直接打印函数名:
printf("add = 0x%x\n", add);
- 通过 int 变量存储:
int address = (int)add; // 强制类型转换
printf("add = 0x%x\n", address);
- 通过函数指针打印:
int (*f)(int a, int b) = add; // 函数指针指向add
printf("f = 0x%x\n", f);
为什么函数指针能调用函数?
当我们执行f = add;
时,函数指针f
存储了add
的地址;之后f(v1, v2)
本质是 “跳转到f
存储的地址(即add
的地址),并传入参数v1
和v2
”,与直接调用add(v1, v2)
完全等价。
🔧 typedef:简化函数指针的定义
函数指针的格式int (*f)(int a, int b)
略显繁琐,尤其在多次使用时。typedef
可以为函数指针类型起一个 “别名”,简化代码。
typedef 的基本用法
typedef
的作用是 “为类型起别名”,类似#define
但更安全(针对类型)。例如:
typedef int INT; // 为int起别名INT
typedef int *PTR_INT; // 为int*起别名PTR_INT// 使用别名定义变量
INT a = 10; // 等价于int a = 10;
PTR_INT p = &a; // 等价于int *p = &a;
为函数指针类型起别名
结合函数指针,我们可以为 “int (*)(int, int)
” 类型起别名:
// 为“参数为两个int、返回值为int的函数指针”起别名type_zhizhen
typedef int (*type_zhizhen)(int a, int b);
之后可用别名定义函数指针,简化计算器代码:
// main.c
#include <stdio.h>// 函数定义(同上)
int add(int a, int b) { return a + b; }
// ...(sub、multi、div定义略)// 为函数指针类型起别名
typedef int (*type_zhizhen)(int a, int b);int main(void)
{char c;type_zhizhen f; // 用别名定义函数指针,等价于int (*f)(int a, int b);printf("enter + - * /\n");scanf("%c", &c);// 给函数指针赋值(同上)switch(c){case '+': f = add; break;// ...(其他case略)default: printf("error\n"); return 0;}int v1 = 4, v2 = 2;printf("v1 %c v2 = %d\n", c, f(v1, v2)); // 直接调用return 0;
}
输入“/”结果如下,代码更简洁易读:
结尾
本文通过计算器的例子,学习了函数指针的核心用法:用它存储函数地址,实现动态选择函数,避免重复逻辑;同时理解了 “函数即地址” 的本质,以及typedef
如何简化函数指针的定义。函数指针在嵌入式开发中应用广泛(比如中断服务函数注册、状态机设计等),是提升代码灵活性的重要工具。
下一篇笔记,我们将深入函数指针的高级应用,比如函数指针数组、通过函数指针实现回调函数等,进一步挖掘它的实用价值。Hello_Embed 会继续带你探索嵌入式 C 语言的进阶技巧,敬请期待~