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