成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇學會回調函數

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

函數指針

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

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

運行結果如下:

為什么需要回調函數

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

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

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

常見的做法,就是界面設置一個回調函數給拷貝文件函數,拷貝文件函數在適當的時候調用這個回調函數來通知界面更新狀態。

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

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

一般的回調函數代碼結構如下:


typedef void (*ReceiveFarmDataFun)();

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;
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函數*/
void main()
{
/*省略其他代碼*/
while(1)
{
if(receive_flg == 1)//通過檢查receive_data判斷是否接收到函數
{
/*通過receive_data數組處理數據*/
receive_flg = 0;
}
}
}

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

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

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

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

/*封裝的函數*/
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指針存儲了串口數據,bufferlen存儲數據長度*/
/*客戶的應用層代碼*/
}
void main()
{
/*省略其他代碼*/
CallbackRegister (uartdatadeal);
while(1)
{
}
}

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

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

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

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

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

后記

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

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

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

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

2021-04-07 13:28:21

函數程序員異步

2021-12-01 11:33:21

函數Min

2022-02-07 11:01:23

ZooKeeper

2022-03-02 11:37:57

參數性能調優

2022-01-02 08:43:46

Python

2021-09-28 08:59:30

復原IP地址

2021-10-14 10:22:19

逃逸JVM性能

2021-10-27 09:59:35

存儲

2021-07-16 22:43:10

Go并發Golang

2023-03-13 21:38:08

TCP數據IP地址

2023-11-01 09:07:01

Spring裝配源碼

2022-10-20 07:39:26

2022-03-11 10:21:30

IO系統日志

2021-04-29 10:18:18

循環依賴數組

2021-10-29 07:35:32

Linux 命令系統

2021-07-02 08:51:29

源碼參數Thread

2022-11-14 08:17:56

2021-07-02 09:45:29

MySQL InnoDB數據

2021-07-06 08:59:18

抽象工廠模式

2023-01-03 08:31:54

Spring讀取器配置
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲一区二区三区四区五区中文 | 欧美在线资源 | 亚洲女人天堂成人av在线 | 国产精品美女在线观看 | 国产日韩久久久久69影院 | 成人污污视频 | 国产一区二区三区视频免费观看 | 成人在线免费视频 | 久久久免费电影 | 91色综合 | 91免费在线 | 欧美区精品 | 91视频在线观看 | 一级做a爰片久久毛片 | 在线a视频网站 | 影音先锋中文字幕在线观看 | 国产精品一区二区欧美 | 精品产国自在拍 | 日本三级网址 | 久久久久久亚洲 | 色婷婷影院 | 亚洲欧美中文日韩在线 | 成人在线免费观看 | 久草在线中文888 | 久久久久久久国产精品视频 | 999久久久免费精品国产 | 国产福利视频网站 | 青青久草 | 日本不卡一二三 | 777zyz色资源站在线观看 | 91色啪 | h在线看| 久久精品这里 | 色婷婷久久久久swag精品 | 成人a免费| 久草在线青青草 | av电影一区| 亚洲久视频 | 天天想天天干 | 男女羞羞视频在线免费观看 | 一区二区免费 |