回调函数的参数 什么是回调函数

大家好 , 我是无量 。
今天继续说回调函数 。
之前写过一篇文章 , 得到了广大老铁工的认可 。
最近几个新生对回调函数有点迷茫 。
我不明白为什么我要做这个圈圈指点点的功能 。
先写文章预热一下 , 晚上再直播和你互动讲解答疑 。
其实不是我想把简单的事情复杂化 , 而是如果你想写出一个好的代码架构 , 回调函数是必不可少的 。
你看看大神写的程序 , 会发现他们都是这么做的 , 比如蓝牙协议栈 , 实时操作系统 , STM32固件库等等 。
每个人的文笔可能不一样 , 但本质是一样的 。
我们先来了解一下回调函数的作用 。
我一般喜欢把函数分为输出型和输入型(个人理解) 。
输出类型:
就是我们主动调用的控制功能 , 比如控制LED灯的开与关 , 控制蜂鸣器响与不响 , 控制液晶显示 , 控制继电器的开与关 。
简单地说 , 我们知道什么时候调用这些函数 。比如在满足一定条件时 , 我们会主动调用这些函数 。
这种函数就是输出函数 。
输入类型:
类型I功能通常用于在不同的之间传输信号和数据 。c文件/层(硬件层和应用层) , 如关键检测和串行数据 。
我们不知道什么时候按键 , 什么时候串口会有数据 , 对吧?
当然 , 我们可以写一个有返回值的函数 , 然后定期检查 , 比如10ms扫描按键 。
unsigned char ScanKey(){//按键检测程序…}
然后我们在主程序中使用:
while(1){ unsigned char key; if(10ms时间到) { Key = ScanKey(); } if(Key == 有效按键值) { //执行按键功能程序 }}
通过这种方式 , 连续扫描按键以检测按键是否被按下 。
当然这种方法也是可以的 , 但是不够专业 , 不够好 。
正因为如此 , 我需要一直判断while循环中Key的值 , 然后根据Key的值判断这个键是否被按下 。一定程度上浪费了cpu资源 。
而且在一些应用场景下 , 这种方法并不容易实现 , 比如串行数据 。while循环中总不能判断有没有新的串行数据进来吧?
我们的理想状态是怎样的?
也就是说 , 如果按了某个键 , 或者有新的数据出现 , 请再次通知我 。
这种通知方式一般称为事件触发 , 即按下一个按钮的事件被触发 , 我来处理 。
所以这个时候回调函数就可以很好的解决这个需求 。
我们以按键为例 。
前面说了 , 每个人写回调函数的风格可能不一样 。STM32固件库中的那些中断处理函数基本都是回调函数 , 不过和我的写作风格还是有一些区别的 。
当我们编写回调函数时 , 我们需要以下步骤:
第一步:
用keyevent _ callback _ t的名字定制一个函数指针类型 。
TYPEDEF void(* KEY event _ CallBack _ t)(KEY _ VALUE _ TYPEDEF keys);
这通常是在头文件中定制的 , 因为其他的 。也将使用c文件 。
这是一个没有返回值的函数指针类型 , 参数是KEY_VALUE_TYPEDEF枚举类型 。
通常 , 这个参数键是我们最终将传递给其他人的信号/数据 。通过回调函数调用c文件 。如果是按键检测 , 就是键值 , 哪个按键被按了 。
我们来看看枚举KEY_VALUE_TYPEDEF的值 。
typedef enum{ KEY_IDLE_VAL, KEY1_CLICK, KEY1_CLICK_RELEASE, KEY1_LONG_PRESS, KEY1_LONG_PRESS_CONTINUOUS, KEY1_LONG_PRESS_RELEASE, //5 KEY2_CLICK, //6 KEY2_CLICK_RELEASE, KEY2_LONG_PRESS, KEY2_LONG_PRESS_CONTINUOUS, KEY2_LONG_PRESS_RELEASE, KEY3_CLICK, //11 KEY3_CLICK_RELEASE, KEY3_LONG_PRESS, KEY3_LONG_PRESS_CONTINUOUS, KEY3_LONG_PRESS_RELEASE, KEY4_CLICK, //16 KEY4_CLICK_RELEASE, KEY4_LONG_PRESS, KEY4_LONG_PRESS_CONTINUOUS, KEY4_LONG_PRESS_RELEASE, KEY5_CLICK, //21 KEY5_CLICK_RELEASE, KEY5_LONG_PRESS, KEY5_LONG_PRESS_CONTINUOUS, KEY5_LONG_PRESS_RELEASE, KEY6_CLICK, //26 KEY6_CLICK_RELEASE, KEY6_LONG_PRESS, KEY6_LONG_PRESS_CONTINUOUS, KEY6_LONG_PRESS_RELEASE,}KEY_VALUE_TYPEDEF;
本项目共有6个按键 , 每个按键需要检测短按、短按释放、长按释放、连续长按五种功能 , 所以有30个不同的枚举值对应不同按键的不同功能 。
第二步:
在定义了函数指针类型之后 , 我们可以通过类型名keyevent _ callback _ t来定义我们的函数指针变量 。
KeyEvent _ CallBack _ t KeyScanCBS
那么KeyScanCBS就是一个函数指针 , 所以它的返回值是void类型 , 它的参数是KEY_VALUE_TYPEDEF枚举类型 。
最后是这个指针指向其他的功能 。c文件 , 从而实现不同 。c文件 , 同时保持良好的可移植性(独立和互不干扰) 。
怎么点?我的方法是专门为这个指针重新定义一个函数 , 方便其他 。要调用的c文件 。我称这个函数为注册函数 。
例如 , 以下功能:
void hal_KeyScanCBSRegister(KeyEvent_CallBack_t pCBS){ if(KeyScanCBS == 0) { KeyScanCBS = pCBS; }}
这个函数的作用是将我们前面定义的KeyScanCBS函数指针指向外部函数地址(也就是指向那个函数的函数名) 。
当然这个功能不是必须的 , 只是我的思维和代码风格 。也可以不单独编写这样的函数 , 只是在使用之前将KeyScanCBS指向一个外部函数 , 或者等待程序崩溃 。哈哈哈 。
第三步:
准备好这些步骤后 , 我们继续说怎么用 。
在我们需要使用按键功能的地方 , 重写相同的功能 。c文件 。
比如app.c文件是产品功能代码(应用层) , 我需要使用应用层的关键功能 。
重写函数时 , 返回值和参数应该与该函数的指针类型相同 。
如果你忘了 , 我们再复习一遍 。
TYPEDEF void(* KEY event _ CallBack _ t)(KEY _ VALUE _ TYPEDEF keys);
没有返回值 , 参数类型为KEY_VALUE_TYPEDEF 。
只有这样才能把这个函数的地址赋给指针KeyScanCBS , 才能正常传输数据 。
重写的函数是通过参数接收硬件层的键值 。如果是串行数据 , 同样如此 , 只是参数不同 。
然后我们在产品函数初始化的函数中直接调用hal_key.c的注册函数 。
将KeyEventHandle函数的地址分配给hal_key.c的KeyScanCBS函数指针
因此 , 最终的KeyScanCBS可以理解为等同于KeyEventHandle函数 。
在hal_key.c文件中 , 我们查看密钥检测和分析程序 , 最后我们执行KeyScanCBS将我们的密钥(密钥值)传输到我们的app.c文件中 。
这样就可以通过事件来驱动 。只有当键被按下 , 并且是真实有效的时候 , 我才会调用KeyScanCBS , 将键值传递给应用层 。
中间两个文件之间没有全局变量依赖 , 可以完全独立 , 可以仔细消化 。
下面是我为什么使用枚举类型作为函数参数的一个细节 。
如果你了解一些模块的二次开发(WiFi , 蓝牙等 。) , 模块的核心代码为你打包成lib库 , 你看不到源代码 。
你只能使用它们的功能 。如果不使用枚举 , 就不知道形参可以传入什么值了吧?
如果我使用枚举 , 我会为你列出所有可用的值 , 并给它们命名 , 让你一眼就能看出它们的意思 。这样方便吗?
好了 , 今天就到这里 。你下去后可以做实验 。
【回调函数的参数 什么是回调函数】原创不容易 。尽量用最通俗的语言表达 。如果对你有帮助 , 请安排三网合一 。

    推荐阅读