C語言學習及應用筆記之七:C語言中的回調函數及使用方式

  咱們在使用C語言實現相對複雜的軟件開發時,常常會碰到使用回調函數的問題。可是回調函數的理解和使用卻不是一件簡單的事,在本篇咱們根據咱們我的的理解和應用經驗對回調函數作簡要的分析。數組

1、什麼是回調函數函數

  既然談到了回調函數,首先咱們就要搞清楚什麼是回調函數。在討論回調函數以前,咱們須要說明另外一個概念,那就是函數指針。什麼是函數指針呢?說的淺顯一點,函數指針就是指向函數的指針,說白了也是一種指針,只是它指向的不是整型,字符型等數據量,而是指向函數。在C中,每一個函數在編譯後都是存儲在內存中,而且每一個函數都有一個入口地址,根據這個地址,咱們即可以訪問並使用這個函數。函數指針就是指向這個入口地址,從而調用這個函數。ui

  一樣回調函數就是一個經過函數指針調用的函數。若是咱們把函數的指針(指向函數入口地址)做爲參數傳遞給另外一個函數,而接收這個參數的函數在其運行過程當中,反過來使用這個指針調用其所指向的函數,咱們就把這個被經過函數指針調用的函數稱之爲回調函數。spa

  從上述描述咱們能夠知道,回調函數有別於通常意義上的函數調用方式。它通常不是由該函數的實現方直接調用,而是由已經存在的其它對象間接調用它。並且回調函數的調用是調用方所須要的,可是其具體實現倒是很是靈活的,咱們能夠根據須要來實現它,只要調用的格式相符,咱們不須要去考慮調用他的對象的具體內容。設計

2、爲什麼使用回調函數指針

  前面咱們簡單介紹了回調函數,那咱們爲何須要使用回調函數呢?既然是用它,固然是有使用的理由。接下來咱們簡單的討論一下使用回調函數的優點所在。code

  首先,可使上層的應用更完整,但又不須要考慮底層的實現細節。好比咱們設計了一個通信應用,但在設計時我並不能肯定底層接口,或者說不想侷限於某一接口。那麼咱們能夠將接口部分的實現留在具體使用中,因此採用回調函數的方式就很是方便。對象

  其次,可使應用更加靈活,這是顯而易見的。好比咱們設計一個通信協議棧,這個協議棧在什麼平臺使用並不侷限,咱們使用回調的方式具體實現平臺相關部分,而協議棧的內核這可使用於多種平臺。blog

  再者,能夠把調用者與被調用者分開,這樣調用者不關心誰是被調用者,也不關心他的具體實現。使得軟件的設計更加獨立,方便與協做或者移植。其實細提及來還有不少,在此僅列舉上述幾點。接口

3、如何使用回調函數

  咱們已經簡單的介紹了什麼事回調函數以及爲何要使用它,接下來咱們說說怎麼使用它。對於使用方式千差萬別,並且每一個使用者都有相應的心得,在這裏咱們之宗解一下咱們平時經常使用的幾種方式。

3.1、以函數參數的形式使用

  在大多數狀況下,咱們可能都是將函數指針做爲參數傳遞給調用者來實現回調。好比咱們聲明以下函數:

  void function1int var1int var2

  void function2void *fcintint),float aint b

  調用時咋使用function2function1ab)就能夠了。固然還有另外一個函數與function1的聲明形式一致,也同樣能夠作爲參數傳遞給function2函數。

  這種方式最好理解,並且函數名不受限制,只要聲明形式一致就能夠了。咱們在外設驅動的調用上會使用這一形式。

3.2、以弱化定義的方式使用

  所謂弱化函數就是調用者以_weak定義一個沒有操做或者默認操做的函數,該函數容許定義與其名稱和形式徹底同樣的函數。若使用者從新定義了該函數則會調用新函數,不然使用_weak修飾的默認函數。在STM32HAL庫中使用了不少這樣的函數,好比各類msp函數。

  首先須要有一個以_weak修飾的函數聲明:

  __weak void SetSingleCoil(uint16_t coilAddress,bool coilValue)

  而在使用時定義一個與其同名且形式同樣的函數:

  void SetSingleCoil(uint16_t coilAddress,bool coilValue),具體個功能有使用者更具須要設定。如上述這個函數就是咱們在調用Modbus協議棧時實現的,每次都不同,根據需求而定。

  這種方式使用雖然方便,但有一個侷限就是必須與原函數聲明一致,且只能有一個。

3.3、以函數註冊的方式使用

  有時候咱們會對一些對象進行封裝,同是將操做函數的函數指針也封裝在內,這樣咱們能夠在使用對象是直接調用其操做。這以方式組要應用於對一些複雜的外設對象的操做。如:網卡對象等,在WIZnet以及LwIP等協議棧中都是以這種方式將網卡密切相關的特定操做以函數指針的方式封裝於對象中。

  固然咱們在開發一些外設的驅動時也可使用這種方式。如咱們開發一個外設驅動,該設備便可使用I2C接口也可以使用SPI接口,咱們要屢次使用該設備,但每次,每一個人使用那種接口是不肯定的,而咱們又想複用這部分驅動,但不是每次都改它,就將其做爲一個對象封裝起來。

  定義一個結構類型,包括包括對象的主要屬性和基本操做接口:

 1   /*定義BMP280操做對象*/
 2 
 3   typedef struct {
 4 
 5     uint8_t chipID;       //芯片ID
 6 
 7     struct Bmp280_Calib_Param caliPara;   //校準參數
 8 
 9     struct Bmp280_Config config;  //配置寄存器
10 
11     struct Bmp280_Ctrl_Meas ctrlMeas;     //測量控制寄存器
12 
13     void (*Read)(uint8_t regAddress,uint8_t *rData,uint16_t rSize);       //讀數據操做指針
14 
15     void (*Write)(uint8_t regAddress,uint8_t command);    //謝數據操做指針
16 
17     void (*Delay)(volatile uint32_t nTime);       //延時操做指針
18 
19   }BMP280Device;

  在使用時,咱們只需聲明某一特定對象,並註冊相應的函數就可使用,調用者並不關心具體接口實現。

3.4、以函數指針類型的方式使用

  以聲明函數指針類型的方式實際上是與函數參數很類式的,也可用於形參聲明,並且更簡潔。但它最主要的優點在於咱們可使用其處理多個回調函數條件調用的問題。

  據好比咱們在處理Modbus協議時咱們在處理不一樣功能嗎的消息時,須要採用不一樣的處理方式,就能夠採用這種方式:

  定義一個枚舉,同時定義一個函數指針數組:

1 void (*HandleSlaveRespond[])(uint8_t *,uint16_t,uint16_t)=
2 
3 {HandleReadCoilStatusRespond,
4 
5                                                             HandleReadInputStatusRespond,
6 
7                                                             HandleReadHoldingRegisterRespond,
8 
9                                                             HandleReadInputRegisterRespond};

  這要咱們經過功能碼的枚舉來調用不一樣的回調函數就很是簡潔了:

  HandleSlaveRespond[fuctionCode](recievedMessage,startAddress,quantity);

  固然,咱們只是討論一種方法,由於使用switch語句同樣能夠達到效果,可是其代碼量倒是相差很遠。

4、總結

  此篇咱們介紹了回調函數及其使用方式,但咱們所掌握的不過冰山之一角。而且具體怎麼使用它是一個見仁見智的論題,用好了天然是給程序增色,但如果隨意使用反倒遊客能會有問題。總而言之,回調函數是一種靈活而有強大的功能,但最終的效果還要看使用者。

歡迎關注:

相關文章
相關標籤/搜索