C語言回撥函式的定義和寫法
1 定義和使用場合
回撥函式是指 使用者自己定義一個函式,實現這個函式的程式內容,然後把這個函式(入口地址)作為引數傳入別人(或系統)的函式中,由別人(或系統)的函式在執行時來呼叫的函式。函式是你實現的,但由別人(或系統)的函式在執行時通過引數傳遞的方式呼叫,這就是所謂的回撥函式。簡單來說,就是由別人的函式執行期間來回調你實現的函式。
這一設計允許了底層程式碼呼叫在高層定義的子程式(如圖1-1所示)。C語言中回撥函式主要通過函式指標的方式實現。
圖1-1 回撥函式在軟體系統的呼叫結果
回撥的用途十分廣泛:[1]
例如,假設有一個函式,其功能為讀取配置檔案並由檔案內容設定對應的選項。若這些選項由
回撥的另一種用途在於處理訊號量。例如一個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 */ }