1. 程式人生 > >C語言回撥函式的定義和寫法

C語言回撥函式的定義和寫法

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 */
}
複製程式碼