深入理解回调函数:写出高效代码架构的必备技巧
大家好,我是乌吉。

让我们继续谈论今天的回调函数。
我之前写过一篇文章,并被许多退伍军人认可。
最近,一些新学生对回调功能有些困惑。
我不明白为什么我需要执行这种功能和指针。
让我们写一篇文章首先热身,然后直播与所有人互动以解释和回答问题。
实际上,这并不是我要使简单的事情复杂化,而是要编写好的代码体系结构,回调函数至关重要。
如果您查看这些大师的程序,您会发现他们都这样做,例如蓝牙协议堆栈,实时操作系统,STM32固件库,等等。
每个人都可以写不同的样式,但是本质是相同的。
让我们首先了解回调函数的作用。
我通常喜欢将功能分为输出类型和输入类型(个人理解)。
输出类型:
这是我们积极调用的控制函数,例如控制LED灯打开和关闭,控制蜂鸣器响起而不是环,控制LCD显示屏以及控制继电器以拉入并断开连接。
简而言之,我们知道何时将这些功能称为这些功能。例如,当满足某些条件时,我们将主动使用这些功能。
这种功能是输出函数。
输入类型:
输入型功能通常用于在不同的.c文件/不同层(硬件层,应用程序层)(例如密钥检测和串行端口数据)之间传输信号和数据。
我们不知道何时按下按钮,何时将串行端口有数据来临,对吗?
当然,我们可以编写带有返回值的函数,然后定期检查它,例如一次扫描键10毫秒。
unsigned char ScanKey()
{
//按键检测程序…
}
然后我们使用:
while(1)
{
unsigned char key;
if(10ms时间到)
{
Key = ScanKey();
}
if(Key == 有效按键值)
{
//执行按键功能程序
}
}
这样,不断扫描键以检查键是否按下。
当然,这种方法也是可能的,但它不够专业,也不够好。
因此,我需要判断密钥的价值在当时的循环中,然后根据密钥值判断是否存在密钥媒体。在一定程度上,它导致了CPU资源的浪费。
此外,在某些应用程序方案中,此方法不容易实现。例如,串行端口数据,您不能总是判断当时是否有新的串行端口数据,对吗?
那么我们的理想状态是什么?
也就是说,如果按下按钮或新数据发布,请再次通知我。
此通知方法通常称为事件触发,这意味着我将在触发关键事件时处理。
因此,回调函数目前可以很好地解决此要求。
让我们以按钮为例。
我之前说,每个人都有不同的编写回调功能的样式。 STM32固件库中的中断处理功能基本上是回调功能,但它们与我的写作风格有所不同。
当我们编写回调函数时,我们需要以下步骤:
第一步:
自定义功能指针类型,类型名称为keyevent_callback_t。
typedef void(*keyevent_callback_t)(key_value_typedef键);
另外,这通常是在标题文件中自定义的,因为还将使用其他.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个不同的枚举值与不同的功能相对应。按钮的不同功能。
步骤2:
自定义功能指针类型后,我们可以通过类型name keyevent_callback_t定义函数指针变量。
keyevent_callback_t keyscancbs;
然后,按键是一个功能指针,因此其返回值是void类型,正式参数为key_value_typedef enum类型。
最后,它是指向其他.c文件的函数,以实现不同.c文件之间的数据传输,同时保持良好的可移植性(独立和非交流)。
那如何指出呢?我的方法是重新定义一个函数并专门针对该指针,以便其他.c文件调用此功能很方便。我称此功能注册功能。
例如,以下功能:
void hal_KeyScanCBSRegister(KeyEvent_CallBack_t pCBS)
{
if(KeyScanCBS == 0)
{
KeyScanCBS = pCBS;
}
}
此函数的功能是指向我们之前定义为外部函数地址的KeysCancbs函数指针(即指向该函数的函数名称)。
当然,此功能不是必需的,这只是我的思维和代码样式。您不能单独编写此类功能。只需将Keyscancbs指向外部功能,否则您将等待程序崩溃Hahaha。
步骤3:
准备这些步骤后,让我们继续谈论如何使用它。
无论我们需要使用密钥的功能,我们都会在该.C文件中重写相同的功能。
例如,文件app.c是产品功能代码(应用程序层),我需要在应用程序层中使用密钥函数。
重写函数时,返回值和形式参数应与功能指针类型相同。
如果您忘记了,让我们再次查看。
typedef void(*keyevent_callback_t)(key_value_typedef键);
没有返回值,正式参数为类型key_value_typedef。
只有这样,您才能将此功能的地址分配给密钥贷方的指针以正常传递数据。
重写的功能是通过正式参数接收硬件层密钥值。如果是串行端口数据,则相同,但是形式参数是不同的。
然后,我们直接在产品功能初始化的功能中直接调用HAL_KEY.C的注册函数。
将KeyEventHandle函数的地址分配给HAL_KEY.C的KeysCancbs功能指针。
因此,最后,按键可以理解为相当于钥匙范围的函数。
在hal_key.c文件中,我们查看密钥检测和解析程序,最后执行按键将键(密钥值)传递到我们的app.c文件。
这样,我可以通过事件来驱动它。只有当按键按下并且是真实有效的时,我才会调用密钥款并将键值传递给应用程序层。
在中间,两个文件之间不依赖全局变量,并且可以完全独立。您可以仔细消化它。
这里有一个细节,这就是为什么我功能的形式参数使用枚举类型的原因。
如果您已将某些模块(WiFi,蓝牙等)连接到二级开发,则您将知道该模块的核心代码被封装到LIB LIB FON FON FON FON FON FON库中,并且您将看不到源代码。
您只能使用他们的功能。如果您不需要枚举,那么您不知道哪些值可以传递到形式参数中,对吗?
如果您使用枚举,我将为您列出所有有用的价值,并给您一个好名字,以便一眼就能说出它的含义。这很方便吗?
好的,今天就这样。如果下降,您可以进行实验。
独创性并不容易,尝试用最受欢迎的语言表达它。如果对您有帮助,请安排一个三合一 ^ ^。