|
关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞
文 | 无际(微信:2777492857)
全文约2639字,阅读大约需要 10 分钟
上周我直播的时候,和我连麦的小哥说,他第一次面试时,面试官问了很多问题,他没回答出来。
其中一个问题,我觉得比较经典,而且尴尬的是,我也忘了。
就是函数指针和指针函数,有什么区别?
后面我大概查阅了一下资料,不就是指针和函数的区别而已嘛...
我和新手一样,很痛恨一些看起来复杂的专业名词,实际上就是纸老虎。
这就回到一个老生常谈的话题了,就是概念,代码之类的东西记不住咋办?
能记住,你就神了,这么多东西,靠记是不现实的。
徐工说,几个月前录过硬件基础的内容,现在也忘了一干二净了。
我以前写过的代码,有时也要静下心钻进去仔细研究,才能看明白。
我相信大多数工程师的记性都没这么好,特别是资深的,因为年纪越大,操心的屁事越多,哪里记得住。
这就是实际的情况,有句话叫熟能生巧,你可以记不住专业名词,但一定要有大量的实践,形成类似于肌肉记忆,这样印象才深刻,然后碰到类似的,你很快就能捡起来,或者举一反三。
我突然感觉面试,有点像学校考试,比如给你一张试卷做,或者问你一些概念性的东西,实际上,哪怕能说出来,可能能混过面试,但不能直接地证明你的动手能力。
下面回到主题,我将详细介绍下函数指针和指针函数的概念。
这两个概念,听起来很像,很容易混淆,我教你个技巧,只看后面两个字,比如指针,函数,就很好区分了。
一、基本概念
1.1 函数指针(Pointer to Function)
函数指针是一个指针,它指向函数的入口地址。
简单来说,就是用一个指针变量来保存函数的地址,通过这个指针可以间接地调用该函数。
如果是我们特训营学过项目3的老铁,应该非常熟悉了,我们大量回调函数的应用,就必须要用到函数指针。
1.2 指针函数(Function Returning Pointer)
指针函数本质是一个函数,只不过这个函数的返回值是一个指针,它返回一个特定类型的地址。
二、详细对比
2.1 函数指针的声明:
返回值类型 (*指针名)(参数列表);示例:int (*operation)(int, int);// 声明一个函数指针
实例:int (*operation)(int, int);
#include
// 定义两个普通函数int add(int a, int b) { return a + b;}
int subtract(int a, int b) { return a - b;}
int main() { // 声明一个函数指针 int (*operation)(int, int); // 指向add函数 operation = add; printf("加法结果:%d
", operation(5, 3)); // 输出:8 // 指向subtract函数 operation = subtract; printf("减法结果:%d
", operation(5, 3)); // 输出:2 return 0;}
输出结果:
mb1htv1bc126401686325.png
2.2 指针函数的声明:
类型* 函数名(参数列表);
示例:int* createArray(int size) //指针函数:返回动态分配的整数数组{}
使用实例:#include #include
// 指针函数:返回动态分配的整数数组int* createArray(int size) { int* arr = (int*)malloc(size * sizeof(int)); for(int i = 0; i { arr = i + 1; } return arr;}
int main() { int size = 5; int* numbers = createArray(size); for(int i = 0; i { printf("%d ", numbers); // 输出:1 2 3 4 5 } free(numbers); return 0;}
输出结果:
v4elihyace26401686425.png
三、实际应用场景
3.1 函数指针的应用
这里强调一下,函数指针是工程师进阶到架构师,必须要掌握的知识点,否则你的程序架构设计不会高级。
函数指针的应用也非常多,比如用函数指针做矩阵管理,我觉几个例子:
例子1:中断服务程序
一般单片机的中断向量表的实现,会采用函数指针,下面是代码模型,仅供参考。
// 中断向量表的实现typedef void (*ISR_Handler)(void);
ISR_Handler ISR_Vector[] = { NMI_Handler, // 不可屏蔽中断 HardFault_Handler, // 硬件错误中断 Timer0_Handler, // 定时器0中断 UART0_Handler, // 串口0中断 ADC0_Handler // ADC0中断};
// 动态修改中断处理函数void setTimerHandler(ISR_Handler newHandler) { ISR_Vector[2] = newHandler; // 修改定时器中断处理函数}这行代码定义了一个函数指针类型ISR_Handler,用于指向中断服务函数。此类函数具有无返回值(void)、无参数(void)、专门用于中断服务程序的特征。
中断向量表包含多个中断服务函数的地址:
NMI_Handler:不可屏蔽中断处理函数
HardFault_Handler:硬件错误中断处理函数
Timer0_Handler:定时器0中断处理函数
UART0_Handler:串口0中断处理函数
ADC0_Handler:ADC0中断处理函数
setTimerHandler是一个可以动态修改中断处理函数的函数,原理是通过更新向量表中的函数指针实现。
这样做有什么好处?
主要体现在以下几个方面:
可维护性:中断向量表集中管理、结构清晰,易于维护、可根据不同场景切换处理函数。
可扩展性:比较模块化、增/减中断处理函数很方便。
类似的用法,还有很多别的应用场景,比如我们无际单片机的项目3:
LED控制:
ldpjna4oaj36401686525.png
按键检测:
sx413iw2cuq6401686625.png
这种方式,非常适合矩阵去管理控制方式相同,或者检测方式相同的场景。
比如一个产品有10个LED灯,每个LED都是高电平点亮,低电平熄灭。
比如一个产品有16个按键,每个按键低电平代表按下,高电平代表释放。
仔细领悟下,用函数指针数组的魅力。
例子2:回调函数示例:
#include
// 回调函数类型typedef void (*CallBack)(int);
// 具体的回调函数实现void onSuccess(int result) { printf("操作成功,结果:%d
", result);}
void onError(int errorCode) { printf("操作失败,错误码:%d
", errorCode);}
// 执行操作的函数void processTask(int value, CallBack success, CallBack error) { if(value > 0) { success(value); } else { error(-1); }}
int main() { processTask(5, onSuccess, onError); // 成功场景 processTask(-5, onSuccess, onError); // 失败场景 return 0;}
输出结果:
nkk52gjtj3d6401686725.png
这个例子,可能不能很好地体现回调函数的优势。
回调函数我觉得最屌的地方,就是我们能写出模块化代码的刚需,从专业术语就是耦合度低的代码,额...不说这万恶的专业术语了。
还是拿我们无际单片机的项目3举例吧。
随便举个按键检测的例子,从大体上我一般把程序分为两层:硬件层和应用层。
硬件层主要负责单片机按键相关引脚的配置,以及按键检测的逻辑代码,然后检测到具体的按键,具体的动作后,再把这个值传给应用层。
硬件层的按键检测代码如下:
l0aqh3vv0o26401686826.png
代码有点长,篇幅有限,我只贴核心的部分。
KeyScanCBS是函数指针,keys是检测到按键具体的状态值,比如key1短按释放。
KeyScanCBS这个函数指针,我们会在应用层给这个函数指针注册。
mclcwbmva3y6401686926.png
这样就能在调用函数指针KeyScanCBS,就相当于调用了app.c文件的KeyEventHandle函数,就可以把keys的值从硬件层,传递给应用层app.c文件了。
如果这样看的有点懵,更详细的可以看我那套程序架构的视频,里面总结了很多实用的高阶编程技巧
rhcezys0uva6401687026.png
这样费劲巴拉的,有啥好处?
就是硬件层和应用层之间的代码,独立程度更高了,不会共享全局变量啥的,移植性会更好,用专业术语来说就是模块化程度高,耦合性低。
还有函数指针用于形参的也挺多,比如我们项目6的OTA升级功能函数,形参就有用到。
btxjyvl2xf36401687126.png
反正,函数指针,必须要掌握!必须要掌握!必须要掌握!
面试能熟练描述我这里写的,相信能加分不少。
3.2 指针函数的应用
指针函数我用的不多,用起来不太习惯,这里就不详细介绍了,类似的应用有动态分配内存管理。
动态内存管理示例:
#include #include #include
// 创建动态字符串char* createString(const char* str) { char* newStr = (char*)malloc(strlen(str) + 1); strcpy(newStr, str); return newStr;}
// 创建动态矩阵int** createMatrix(int rows, int cols) { int** matrix = (int**)malloc(rows * sizeof(int*)); for(int i = 0; i matrix = (int*)malloc(cols * sizeof(int)); } return matrix;}
int main() { char* str = createString("Hello, World!"); printf("%s
", str); free(str); int** matrix = createMatrix(3, 4); // 使用矩阵... // 释放内存 for(int i = 0; i 3; i++) { free(matrix); } free(matrix); return 0;}
四、总结下
4.1 本质区别
函数指针是一个指针变量,存储函数的地址。
指针函数是一个函数,返回值为指针类型。
4.2 使用场景
函数指针主要用于矩阵控制,回调机制等等。
指针函数主要用于动态内存分配、返回复杂数据结构等场景。
4.3 开发建议
掌握这两个知识点,非常重要,是你以后写出高灵活性、高扩展性,高移植性代码的刚需,学习方法是多实践应用。
祝你,代码越来越优雅高级。
end
k1ddnmegbz46401687226.jpg
下面是更多无际原创的个人成长经历、行业经验、技术干货。
1.电子工程师是怎样的成长之路?10年5000字总结
2.如何快速看懂别人的代码和思维
3.单片机开发项目全局变量太多怎么管理?
4.C语言开发单片机为什么大多数都采用全局变量的形式?
5.单片机怎么实现模块化编程?实用程度让人发指!
6.c语言回调函数的使用及实际作用详解
7.手把手教你c语言队列实现代码,通俗易懂超详细!
8.c语言指针用法详解,通俗易懂超详细! |
|