C++回撥函式詳解
概念
一個程式執行時,所有和執行相關的資源都需要被載入到記憶體中,如果在程式中定義了一個函式,那麼在編譯時系統就會為這個函式程式碼分配一段儲存空間,這段儲存空間的首地址稱為這個函式的地址。而且函式名錶示的就是這個地址。既然是地址我們就可以定義一個指標變數來存放,這個指標變數就叫作函式指標變數,簡稱函式指標。
使用函式指標實現函式呼叫
1 #include <iostream> 2 3 typedef void (*PINVOKE)(const char *str); 4 5 void Invoke(const char *str) 6 { 7 std::cout << str << std::endl;8 } 9 10 int main() 11 { 12 PINVOKE fp = Invoke; 13 fp("Hello world."); 14 15 return 0; 16 }
說明
函式指標與函式宣告的唯一區別就是用指標名(*fp
)代替了函式名Invoke
,然後進行賦值fp = Invoke
就可以進行函式指標的呼叫了。宣告函式指標時要求函式返回值型別、引數個數、引數型別等與已定義函式保持一致。注意,函式指標必須用括號括起來 void (*fp)(char *s)
。
2、回撥函式
概念
宣告並定義一個函式A
,然後把函式A
的指標作為引數傳入其他的函式(或系統)中,其他的函式(或系統)在執行時通過函式指標呼叫函式A
示例
1 #include <iostream> 2 3 typedef void (*CALLBACKFUN)(const char *str); 4 5 void PrintText(CALLBACKFUN fp, const char *str) 6 { 7 fp(str); 8 } 9 10 void Invoke(const char *str) 11 { 12 std::cout << str << std::endl; 13} 14 15 int main() 16 { 17 PrintText(Invoke, "Hello world."); 18 return 0; 19 }
類成員函式作為回撥函式
回撥函式是基於C
- Windows SDK
的技術,不是針對C++
的,程式設計師可以將一個C
函式直接作為回撥函式,但是如果試圖直接使用C++
的成員函式作為回撥函式將發生錯誤,因為普通的C++
成員函式都隱含一個傳遞函式作為引數,即this
指標,C++
通過向其他成員函式傳遞一個指向自身的指標來實現程式函式訪問C++
資料成員。所以實現類成員函式作為回撥函式有兩種途徑:1、不使用成員函式(使用友元操作符friend
的C
函式訪問類的資料成員);2、使用靜態成員函式。
例如:
1 #include <iostream> 2 3 class CPrintString 4 { 5 public: 6 void PrintText(const char *str) 7 { 8 std::cout << str << std::endl; 9 } 10 11 static void SPrintText(void *pPs, const char *str) 12 { 13 CPrintString *pThis = static_cast<CPrintString *>(pPs); 14 if(NULL == pPs) 15 { 16 return; 17 } 18 pThis->PrintText(str); 19 } 20 }; 21 22 typedef void (*PRINTTEXT)(void *pPs, const char *str); 23 24 void CallBackFun(void *pPs, const char *str, PRINTTEXT fp) 25 { 26 fp(pPs, str); 27 } 28 29 int main() 30 { 31 CPrintString obj; 32 CallBackFun((void *)&obj, "Hello world.", CPrintString::SPrintText); 33 34 return 0; 35 }
3、為什麼使用回撥函式
一般情況下,回撥函式能被普通函式替換,但回撥函式最重要的作用是解耦,在這一特點上普通函式代替不了回撥函式。
例子:
1 #include <stdio.h> 2 #include <softwareLib.h> // 包含 Library Function 所在讀得 Software library 庫的標頭檔案 3 4 int Callback() // Callback Function 5 { 6 // TODO 7 return 0; 8 } 9 10 int main() // Main program 11 { 12 // TODO 13 Library(Callback); 14 // TODO 15 return 0; 16 }
乍一看,回撥似乎只是函式間的呼叫,和普通函式呼叫沒啥區別,但仔細一看,可以發現兩者之間的一個關鍵的不同:在回撥中,主程式把回撥函式像引數一樣傳入庫函式。這樣一來,只要我們改變傳進庫函式的引數,就可以實現不同的功能,並且絲毫不需要修改庫函式的實現,這就是解耦。再仔細看看,主函式和回撥函式是在同一層的,而庫函式在另外一層,一般情況下庫函式對開發人員並不可見,庫函式的實現一般不會被修改,也就是說不能通過修改庫函式讓庫函式呼叫普通函式那樣實現,那就只能通過傳入不同的回撥函數了,這在企業開發中非常常見。