概念
一個程序運行時,所有和運行相關的資源都需要被加載到內存中,如果在程序中定義了一個函數,那么在編譯時系統就會為這個函數代碼分配一段存儲空間,這段存儲空間的首地址稱為這個函數的地址。而且函數名表示的就是這個地址。既然是地址我們就可以定義一個指針變量來存放,這個指針變量就叫作函數指針變量,簡稱函數指針。
使用函數指針實現函數調用
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 }
乍一看,回調似乎只是函數間的調用,和普通函數調用沒啥區別,但仔細一看,可以發現兩者之間的一個關鍵的不同:在回調中,主程序把回調函數像參數一樣傳入庫函數。這樣一來,只要我們改變傳進庫函數的參數,就可以實現不同的功能,並且絲毫不需要修改庫函數的實現,這就是解耦。再仔細看看,主函數和回調函數是在同一層的,而庫函數在另外一層,一般情況下庫函數對開發人員並不可見,庫函數的實現一般不會被修改,也就是說不能通過修改庫函數讓庫函數調用普通函數那樣實現,那就只能通過傳入不同的回調函數了,這在企業開發中非常常見。