天天看點

回調函數原來這麼容易了解

1 什麼是回調函數?

首先什麼是“回調”呢?

我的了解是:把一段可執行的代碼像參數傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被調用執行,這就叫做回調。

如果代碼立即被執行就稱為同步回調,如果過後再執行,則稱之為異步回調。

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(位址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。

回調函數不是由該函數的實作方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。

2 為什麼要用回調函數?

因為可以把調用者與被調用者分開,是以調用者不關心誰是被調用者。它隻需知道存在一個具有特定原型和限制條件的被調用函數。

簡而言之,回調函數就是允許使用者把需要調用的方法的指針作為參數傳遞給一個函數,以便該函數在處理相似事件的時候可以靈活的使用不同的方法。

int Callback()    ///< 回調函數
{
    // TODO
    return 0;
}
int main()     ///<  主函數
{
    // TODO
    Library(Callback);  ///< 庫函數通過函數指針進行回調
    // TODO
    return 0;
}
回調似乎隻是函數間的調用,和普通函數調用沒啥差別。
但仔細看,可以發現兩者之間的一個關鍵的不同:在回調中,主程式把回調函數像參數一樣傳入庫函數。
這樣一來,隻要我們改變傳進庫函數的參數,就可以實作不同的功能,這樣有沒有覺得很靈活?并且當庫函數很複雜或者不可見的時候利用回調函數就顯得十分優秀。
3 怎麼使用回調函數?
int Callback_1(int a)   ///< 回調函數1
{
    printf("Hello, this is Callback_1: a = %d ", a);
    return 0;
}
int Callback_2(int b)  ///< 回調函數2
{
    printf("Hello, this is Callback_2: b = %d ", b);
    return 0;
}
int Callback_3(int c)   ///< 回調函數3
{
    printf("Hello, this is Callback_3: c = %d ", c);
    return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 注意這裡用到的函數指針定義
{
    Callback(x);
}
int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}      

如上述代碼:可以看到,Handle()函數裡面的參數是一個指針,在main()函數裡調用Handle()函數的時候,給它傳入了函數Callback_1()/Callback_2()/Callback_3()的函數名,這時候的函數名就是對應函數的指針,也就是說,回調函數其實就是函數指針的一種用法。

4 回調函數執行個體(很有用)

一個GPRS子產品聯網的小項目,使用過的同學大概知道2G、4G、NB等子產品要想實作無線聯網功能都需要經曆子產品上電初始化、注冊網絡、查詢網絡資訊品質、連接配接伺服器等步驟,這裡的的例子就是,利用一個狀态機函數(根據不同狀态依次調用不同實作方法的函數),通過回調函數的方式依次調用不同的函數,實作子產品聯網功能,如下:

/*********  工作狀态處理  *********/
typedef struct
{
 uint8_t mStatus;
 uint8_t (* Funtion)(void); //函數指針的形式
} M26_WorkStatus_TypeDef;  //M26的工作狀态集合調用函數
/**********************************************
** >M26工作狀态集合函數
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{    
    {GPRS_NETWORK_CLOSE,  M26_PWRKEY_Off  }, //子產品關機
    {GPRS_NETWORK_OPEN,  M26_PWRKEY_On  }, //子產品開機
    {GPRS_NETWORK_Start,   M26_Work_Init  }, //管腳初始化
    {GPRS_NETWORK_CONF,  M26_NET_Config  }, /AT指令配置
    {GPRS_NETWORK_LINK_CTC,  M26_LINK_CTC  }, //連接配接排程中心  
    {GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC  },  //等待排程中心回複 
    {GPRS_NETWORK_LINK_FEM, M26_LINK_FEM  }, //連接配接前置機
    {GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM  }, //等待前置機回複
    {GPRS_NETWORK_COMM,  M26_COMM   }, //正常工作    
    {GPRS_NETWORK_WAIT_Sig,  M26_WAIT_Sig  },  //等待信号回複
    {GPRS_NETWORK_GetSignal,  M26_GetSignal  }, //擷取信号值
    {GPRS_NETWORK_RESTART,  M26_RESET   }, //子產品重新開機
}/**********************************************
** >M26子產品工作狀态機,依次調用裡面的12個函數   
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
    uint8_t i = 0;
    for(i = 0; i < 12; i++)
    {
        if(Start == M26_WorkStatus_Tab[i].mStatus)
        {          
      return M26_WorkStatus_Tab[i].Funtion();
        }
    }
    return 0;
}      

是以,如果有人想做個NB子產品聯網項目,可以copy上面的架構,隻需要修改回調函數内部的具體實作,或者增加、減少回調函數,就可以很簡潔快速的實作子產品聯網。