C語言中的回調函數(Callback Function)


1 定義和使用場合

回調函數是指 使用者自己定義一個函數,實現這個函數的程序內容,然后把這個函數(入口地址)作為參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。函數是你實現的,但由別人(或系統)的函數在運行時通過參數傳遞的方式調用,這就是所謂的回調函數。簡單來說,就是由別人的函數運行期間來回調你實現的函數。

這一設計允許了底層代碼調用在高層定義的子程序(如圖1-1所示)。C語言中回調函數主要通過函數指針的方式實現。

 

圖1-1 回調函數在軟件系統的調用結果

 

回調的用途十分廣泛:[1]

例如,假設有一個函數,其功能為讀取配置文件並由文件內容設置對應的選項。若這些選項由散列值(hash function)所標記,則讓這個函數接受一個回調會使得程序設計更加靈活:函數的調用者可以使用所希望的散列算法,該算法由一個將選項名轉變為散列值的回調函數實現;因此,回調允許函數調用者在運行時調整原始函數的行為

回調的另一種用途在於處理信號量。例如一個POSIX程序可能在收到SIGTERM信號時不願立即終止;為了保證一切運行良好,該程序可以將清理函數注冊為SIGTERM信號對應的回調。

回調亦可以用於控制一個函數是否作為:Xlib允許自定義的謂詞(NSPredicate)用於決定程序是否希望處理特定的事件。

#include <iostream>
#include <string>
using namespace std;

typedef void (*FP)(char* s);    //結構體表示函數指針
void f1(char* s){cout<<s;}
void f2(char* s){cout<<s;}
void f3(char* s){cout<<s;}

int main(int argc,char* argv[])
{
    int funcselector=0;           //定義一個整數用於控制待執行的函數
    void* a[]={f1,f2,f3};   //定義了指針數組,這里a是一個普通指針
    a[0]("Hello World!\n"); //編譯錯誤,指針數組不能用下標的方式來調用函數

    FP f[]={f1,f2,f3};      //定義一個函數指針的數組,這里的f是一個函數指針
    
    /* Handle of funselector */       //此處用於處理funselector,控制待執行的函數
    f[funselector]("Hello World!\n"); //正確,函數指針的數組進行下標操作可以進行函數的間接調用
    
    return 0;
}     

上面一個例子中提現了回調函數的部分作用。這里f1,f2,f3表示三個功能不相同的函數(舉例說明:f1實現最大值輸出,f2實現平均值輸出,f3實現最小值輸出)。總結一下回調函數的一些優勢:

采用funcselector作為標志量,選擇待執行的函數很方便的控制了函數的流程和工序。

f1,f2,f3三個特定函數模塊化明顯,便於設計者去維護、修改。如圖1-1所示,很多系統中software library會完全封裝,這樣開發者只能通過回調函數去修改函數功能。

分析函數思路更加清晰,在lwip中大量使用回調函數,開發者可以根據回調函數的調用流程分析系統結構。

 

2 結構解析

回調函數主要結構有三部分組成:主函數、調用函數和被調函數(如圖1-1所示)。C語言中,被調函數通常以函數指針(指向對應函數的入口地址)的形式出現。 

這里給出一個最簡單的回調函數結構,並解析相關數據結構。

//定義回調函數
void PrintfText() 
{
    printf("Hello World!\n");
}

//定義實現回調函數的"調用函數"
void CallPrintfText(void (*callfuct)())
{
    callfuct();
}

//實現函數回調
int main(int argc,char* argv[])
{
    CallPrintfText(PrintfText);
    return 0;
}

調用函數向其函數中傳遞 void (*callfuct)(void) 這是一個 void callfuct(void) 函數的入口地址,即PC指針可以通過移動到該地址執行void callfuct(void) 函數,可以通過類比數組來理解。

實現函數調用中,函數調用了“調用函數”,再在其中進一步調用被“調用函數”。相比於主函數直接調用“被調函數”,這種方法為使用者,而不是開發者提供了靈活的接口。另外,函數入口可以像變量一樣設定同樣為開發者提供了靈活性。

 

3 實例分析

 這里分析一個lwip中較為復雜的回調函數使用范例:

void httpd_init(void)
{
    struct tcp_pcb * pcb;
    pcb = tcp_new();
    tcp_bind(pcb,IP_ADDR_ANY,80);
    pcb = tcp_listen(pcb);
    tcp_accept(pcb, http_accept);        
}

void
tcp_accept(struct tcp_pcb * pcb, err_t(* accept)(void *arg, struct tcp_pcb *newpcb, err_t err))

static err_t http_accept(void *arg, struct tcp_pcb * pcb, err_t err)
{
    /* set the prio of callback function, important */
    tcp_setprio(pcb, TCP_PRIO_MIN);
    tcp_recv(pcb, http_recv);
    return ERR_OK;    
}    

void 
tcp_recv(struct tcp_pcb * pcb, err_t (* recv)(void * arg, struct tcp_pcb * tpcb, struct pbuf * p, err_t err))

static err_t http_recv(void *arg, struct tcp_pcb * pcb, struct pbuf *p, err_t err)
{
    /* html handler by user's definition */
    /* use tcp_write(pcb, message, sizeof message, 0) to send message */
}

這里調用兩個回調函數,模塊化分離了tcp和http,感興趣可以看看lwip的RAW部分。

 

4 參考資料

[1] https://zh.wikipedia.org/wiki/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0

[2] http://partow.net/programming/templatecallback/index.html

[3] http://www.partow.net/programming/hashfunctions/index.html

[4] http://blog.csdn.net/callmeback/article/details/4242260

[5] http://www.cnblogs.com/chenyuming507950417/archive/2012/01/02/2310114.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM