偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一篇學會回調函數(shù)

開發(fā) 前端
關于回調函數(shù),我的態(tài)度是:回調函數(shù)可以使我們的代碼更高效且更易于維護,降低耦合。明智地使用它們很重要,否則過度使用回調(函數(shù)指針)會使代碼難以進行排查和。

函數(shù)指針

學習回調函數(shù),其實就是函數(shù)指針的應用,關于函數(shù)指針在之前的文章《??指針與函數(shù)??》中有詳細的講解,這里不再展開詳解,重新貼一下之前文章中函數(shù)指針的示例代碼:

#include <stdio.h>
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int); /* ②. 定義一個函數(shù)指針類型FunType,與①函數(shù)類型一致 */
void CallMyFun(FunType fp, int x);
int main(int argc, char *argv[])
{
CallMyFun(MyFun1, 10); /* ⑤. 通過CallMyFun函數(shù)分別調用三個不同的函數(shù) */
CallMyFun(MyFun2, 20);
CallMyFun(MyFun3, 30);
}
void CallMyFun(FunType fp, int x) /* ③. 參數(shù)fp的類型是FunType。*/
{
fp(x); /* ④. 通過fp的指針執(zhí)行傳遞進來的函數(shù),注意fp所指的函數(shù)是有一個參數(shù)的。 */
}
void MyFun1(int x) /* ①. 這是個有一個參數(shù)的函數(shù),以下兩個函數(shù)也相同。 */
{
printf("MyFun1:%d\n", x);
}
void MyFun2(int x)
{
printf("MyFun2:%d\n", x);
}
void MyFun3(int x)
{
printf("MyFun3:%d\n", x);
}

運行結果如下:

為什么需要回調函數(shù)

這里先說一下軟件分層的問題,軟件分層的一般原則是:上層可以直接調用下層的函數(shù),下層則不能直接調用上層的函數(shù)。這句話說來簡單,在現(xiàn)實中,下層常常要反過來調用上層的函數(shù)。

比如你在拷貝文件時,在界面層調用一個拷貝文件函數(shù)。界面層是上層,拷貝文件函數(shù)是下層,上層調用下層,理所當然。但是如果你想在拷貝文件時還要更新進度條,問題就來了。

一方面,只有拷貝文件函數(shù)才知道拷貝的進度,但它不能去更新界面的進度條。另外一方面,界面知道如何去更新進度條,但它又不知道拷貝的進度。怎么辦?

常見的做法,就是界面設置一個回調函數(shù)給拷貝文件函數(shù),拷貝文件函數(shù)在適當?shù)臅r候調用這個回調函數(shù)來通知界面更新狀態(tài)。

上面主要說的一個大型軟件分層理念,作為嵌入式開發(fā)程序員,特別是單片機的開發(fā)中,由于和硬件結合緊密且需要快速響應,軟件結構大部分是面向過程開發(fā)的,回調函數(shù)使用頻率并不高。但在軟件中使用回調函數(shù),可以讓軟件更加模塊化。

上圖形象展示了回調函數(shù)的作用,上面說到了軟件分層,在嵌入式代碼中我們一般將和硬件交互的代碼稱為硬件層,業(yè)務邏輯代碼稱為應用層代碼,對于優(yōu)秀的的嵌入式代碼,一般要求硬件層和應用層代碼分開。

一般的回調函數(shù)代碼結構如下:


typedef void (*ReceiveFarmDataFun)();

static CallbackReceive_t HandlerCompleted;

/*用來注冊回調函數(shù)的功能函數(shù)*/
void CallbackRegister (CallbackFunc_t callback_func) {
HandlerCompleted = callback_func;
}

串口應用

在嵌入式應用中,串口通信是很經(jīng)典且常用的外設,舉一個簡單的栗子,接收的串口數(shù)據(jù)幀頭是@,幀尾是*。中間數(shù)據(jù)不可能出現(xiàn)@和*。那么一般情況下代碼如下編寫。


/*串口中斷函數(shù)*/
uint8_t receive_flg = 0;
uint8_t receive_data[100];
uint8_t USART1_data = 0;
uint8_t USART1_data_len = 0;
uint8_t USART1_receive_sta = 0;
void USART1_IRQHandler(void)
{
uint8_t data_tmp;
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
{
data_tmp = USART_ReceiveData(USART1);
if((data_tmp == '*')&&(USART1_receive_sta == 1))
{
receive_flg = 1;
USART1_receive_sta = 0;
receive_data[USART1_data_len++] = data_tmp;
}
if(receive_flg == 0){
if(data_tmp == '@')
{
USART1_receive_sta = 1;
USART1_data_len = 0;
}
if(USART1_receive_sta)
receive_data[USART1_data_len++] = data_tmp;
if(USART1_data_len > (100-1))
{
receive_flg = 0;
USART1_receive_sta = 0;
}
}
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}
/*應用層代碼,簡單化->在main函數(shù)*/
void main()
{
/*省略其他代碼*/
while(1)
{
if(receive_flg == 1)//通過檢查receive_data判斷是否接收到函數(shù)
{
/*通過receive_data數(shù)組處理數(shù)據(jù)*/
receive_flg = 0;
}
}
}

這樣實現(xiàn)功能是沒有問題的,在我接觸到很多的項目中的確是類似的架構,但是它的移植性較差。

還有一種情況,那就是如果你接到需求把硬件層封裝給客戶使用,不讓客戶看到源碼,封裝成庫,起到"保護通訊協(xié)議"的目的,那么你要告訴客戶,需要判斷receive_flg變量,然后讀取receive_data數(shù)組的內容???

不得不說,你這樣干是可以的,但是大部分公司不會這樣干的。這時候可以使用回調函數(shù)來解決這個問題。

/*開放給客戶的頭文件*/
/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
typedef void (*ReceiveFarmDataFun)(uint8_t *buff,uint32_t bufferlen);
extern void CallbackRegister (CallbackFunc_t callback_func);

/*封裝的函數(shù)*/
static CallbackReceive_t HandlerCompleted;
void CallbackRegister (CallbackFunc_t callback_func) {
HandlerCompleted = callback_func;
}
uint8_t receive_flg = 0;
uint8_t receive_data[100];
uint8_t USART1_data = 0;
uint8_t USART1_data_len = 0;
uint8_t USART1_receive_sta = 0;
void USART1_IRQHandler(void)
{
uint8_t data_tmp;
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
{
data_tmp = USART_ReceiveData(USART1);
if((data_tmp == '*')&&(USART1_receive_sta == 1))
{
receive_flg = 1;
USART1_receive_sta = 0;
HandlerCompleted(receive_data,USART1_data_len);
}
if(receive_flg == 0){
if(data_tmp == '@')
{
USART1_receive_sta = 1;
USART1_data_len = 0;
}
if(USART1_receive_sta)
receive_data[USART1_data_len++] = data_tmp;
if(USART1_data_len > (100-1))
{
receive_flg = 0;
USART1_receive_sta = 0;
}
}
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}

那么客戶拿到的有用信息如下:

typedef void (*ReceiveFarmDataFun)(uint8_t *buff,uint32_t bufferlen);
extern void CallbackRegister (CallbackFunc_t callback_func);

客戶可以寫如下代碼:

void uartdatadeal(uint8_t *buff,uint32_t bufferlen)
{
/*buff指針存儲了串口數(shù)據(jù),bufferlen存儲數(shù)據(jù)長度*/
/*客戶的應用層代碼*/
}
void main()
{
/*省略其他代碼*/
CallbackRegister (uartdatadeal);
while(1)
{
}
}

這樣的話,就可以解決上述問題,客戶只要注冊一下串口接收的函數(shù),當接收到有效數(shù)據(jù)后,就可以跳轉到用戶的代碼,而你可以將自己的硬件層封裝起來。

看到這里可能有嵌入式大佬意識到某些問題了,這樣寫代碼,數(shù)據(jù)處理的函數(shù)就等于在中斷里了,這是不合理的啊。

是的,是有這個問題,所以給客戶的庫文件必須說明這一點,讓客戶自行選擇,客戶不想在中斷中執(zhí)行,可以再按照我們一開始的邏輯寫啊,如下:

void uartdatadeal(uint8_t *buff,uint32_t bufferlen)
{
/*buff指針存儲了串口數(shù)據(jù),bufferlen存儲數(shù)據(jù)長度*/
receive_flg = 1;
}
void main()
{
/*省略其他代碼*/
CallbackRegister (uartdatadeal);
while(1)
{
if(receive_flg == 1)
{
/*處理數(shù)據(jù)*/
receive_flg = 0;
}
}
}

事實上,芯片/模塊廠家寫SDK經(jīng)常這樣做,一些大型的開源庫也會這樣用,典型的如lwip庫。

后記

讀到這里的同學可能覺得這完全是“脫褲子放屁”啊,這屬于“炫技”啊,沒什么用啊。誠然在很多應用中,特別是一些單片機項目中,代碼量不大,使用類似receive_flg全局變量控制,代碼結構也清晰啊。

并且項目不需封裝庫給客戶,一個單片機軟件開發(fā)工程師可以吃透整個項目的代碼,根本不需要這樣的“騷操作”。

關于回調函數(shù),我的態(tài)度是:回調函數(shù)可以使我們的代碼更高效且更易于維護,降低耦合。明智地使用它們很重要,否則過度使用回調(函數(shù)指針)會使代碼難以進行排查和調試。

責任編輯:武曉燕 來源: 知曉編程
相關推薦

2021-04-07 13:28:21

函數(shù)程序員異步

2021-12-01 11:33:21

函數(shù)Min

2022-03-02 11:37:57

參數(shù)性能調優(yōu)

2022-02-07 11:01:23

ZooKeeper

2022-01-02 08:43:46

Python

2021-09-28 08:59:30

復原IP地址

2021-10-27 09:59:35

存儲

2021-07-16 22:43:10

Go并發(fā)Golang

2022-10-20 07:39:26

2023-03-13 21:38:08

TCP數(shù)據(jù)IP地址

2022-03-11 10:21:30

IO系統(tǒng)日志

2021-10-29 07:35:32

Linux 命令系統(tǒng)

2023-11-01 09:07:01

Spring裝配源碼

2021-10-14 10:22:19

逃逸JVM性能

2022-11-14 08:17:56

2021-04-29 10:18:18

循環(huán)依賴數(shù)組

2021-07-02 08:51:29

源碼參數(shù)Thread

2021-07-02 09:45:29

MySQL InnoDB數(shù)據(jù)

2023-01-03 08:31:54

Spring讀取器配置

2022-08-26 09:29:01

Kubernetes策略Master
點贊
收藏

51CTO技術棧公眾號