当前位置: 首页 > news >正文

嵌入式 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等函数
打印函数地址的三种方式

由于函数名即地址,我们可以通过以下方式获取函数地址:

  1. 直接打印函数名printf("add = 0x%x\n", add);
  2. 通过 int 变量存储
int address = (int)add;  // 强制类型转换
printf("add = 0x%x\n", address);
  1. 通过函数指针打印
int (*f)(int a, int b) = add;  // 函数指针指向add
printf("f = 0x%x\n", f);
为什么函数指针能调用函数?

当我们执行f = add;时,函数指针f存储了add的地址;之后f(v1, v2)本质是 “跳转到f存储的地址(即add的地址),并传入参数v1v2”,与直接调用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 语言的进阶技巧,敬请期待~

http://www.lryc.cn/news/610128.html

相关文章:

  • SurferCloud vs LightNode 海外云服务商详细对比
  • 【无标题】标准 I/O 中的一些函数,按功能分类说明其用法和特点
  • [特殊字符] 50 天 50 个项目 — 完结篇
  • 【Docker安装】Ubuntu 24.04.2 LTS系统下安装Docker环境——指定APT源安装方式
  • 基于MobileNet卷积神经网络和Xception神经网络算法的人脸表情识别系统的设计与实现
  • C语言的控制语句
  • 每日一leetcode:移动零
  • 【Java】HashMap线程安全吗?
  • allegro建库--1
  • 【云馨AI-大模型】2025年8月第一周AI浪潮席卷全球:创新与政策双轮驱动
  • MLS平滑滤波
  • 洛谷 P3373 【模板】线段树 2- 普及+/提高
  • 《Python 实用项目与工具制作指南》· 3.1 实战·开发题目数据生成器
  • 思科 UCS Fabric Interconnect 和 UCS Manager 简介
  • 比起登天,孙宇晨更需要安稳着陆
  • C语言编程中常用的预定义宏
  • 浅谈 Python 中的 next() 函数 —— 迭代器的驱动引擎
  • 【深度学习新浪潮】近三年城市级数字孪生的研究进展一览
  • push/pop字节对齐使用场景
  • Next Terminal 实战:内网无密码安全登录
  • cocos2 场景跳转传参
  • 佰力博检测与您探讨介温谱和介电谱的区别?
  • 【实战】Dify从0到100进阶--中药科普助手(1)
  • 7.1、《软件工程》-软件生命周期-CMM-开发模型
  • 【2025/08/04】GitHub 今日热门项目
  • 【2025-08-04 Java学习小记】
  • Linux磁盘分区与挂载完全指南
  • Java基础学习(一):类名规范、返回值、注释、数据类型
  • 使用1panel将http升级至https的过程
  • javacc学习笔记 03、编译原理实践 - JavaCC解析表达式并生成抽象语法树