当前位置: 首页 > 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.dtcms.com/a/315349.html

相关文章:

  • 文本转语音(TTS)脚本
  • 【项目实践】在系统接入天气api,根据当前天气提醒,做好plan
  • C语言的控制语句
  • 16day-人工智学习-机器学习-特征工程
  • 【世纪龙科技】虚拟技术助力职教汽车自动变速器拆装虚拟实训软件
  • RFID技术在汽车倍速链中的应用:驱动智能制造的隐形引擎
  • Windows/Linux入侵排查
  • CPP学习之多态
  • Python高频元素分析技术:高效找出序列中出现次数最多的元素
  • 【Unity3D实例-功能-镜头】第三人称视觉
  • FeiQ飞秋安装教程:FeiQ.1060559168 详细安装步骤(附注意事项)​
  • 【QT】常⽤控件详解(三)常用按钮控件PushButton RadioButton CheckButton Tool Button
  • 茗鹤工业低代码可视化技术开发平台
  • 网络相关命令
  • 全国计算机二级C语言二级考试通关笔记
  • 风光储并网协同运行simulink仿真模型实现
  • [找出字符串中第一个匹配项的下标]
  • MiDSS复现
  • Codeforces Round 1010 (Div. 2, Unrated)
  • 8.4IO进程线程——进程
  • MySQL 基本操作入门指南
  • 代码随想录day55图论5
  • 通往L4之路:构建自我进化的智能驾驶决策大脑
  • Dubbo 3.x源码(32)—Dubbo Provider处理服务调用请求源码
  • CSS 安卓应用卸载碎片化动画效果
  • pyqt5-tools/pyqt6-tools 安装失败,解决办法
  • 【秋招笔试】2025.08.03虾皮秋招笔试-第三题
  • 7.2 I/O接口 (答案见原书 P305)
  • 大模型部署、nvidia-smi、token数
  • Java项目:基于SSM框架实现的商铺租赁管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告+任务书+远程部署】