函數指針教程 原版:http://www.newty.de/fpt/index.html
譯者:Lymons Lau
導引
|
1. 函數指針簡介
|
2. C 和 C++ 函數指針語法
|
3. 在C 和 C++里怎么實現(xiàn)回調函數 ?
|
4. 封裝C 和 C++函數指針的仿函數
|
5. 相關鏈接
|
函數指針提供了一些極其有趣,有效和絕妙的編程技術。你能用它代替switch/if語句來實現(xiàn)你自己的晚綁定(late-binding)或者作為回調(callback)來使用。不幸的是–可能由于它的語法比較復雜–幾乎所有的電腦書籍和文檔上都講解的不多。即便如此,它們也只是做了相當簡單和膚淺的說明。而對于函數指針你只需要明白它是什么以及它的語法,因為它和一般的指針比起來從來不用關心內存的分配和釋放,所以它被使用的時候是不易產生錯誤的。但你要注意的是: 要時常問自己是否真的需要函數指針。因為雖然用它來實現(xiàn)晚綁定也很漂亮,但用既存的C++數據結構的話會使代碼更可讀和更簡潔。另外,晚綁定的一方面實際上就是運行期(runtime): 如果你調用了一個虛擬函數,你的程序會根據一個存儲所有函數的虛擬表(V-Table)自己來確定到底真正調用的是哪一個。這就要花費一些時間而用函數指針代替虛擬函數的話有可能會節(jié)省一些時間。BTW: 現(xiàn)代的編譯器在這方面都做得非常好!就那我的Borland編譯器來說這個時間就比調用一次虛擬函數能節(jié)省2%。
注:晚捆綁(late binding)可能來自c++的術語,也稱為動態(tài)捆綁(dynamic binding),它主要是來實現(xiàn)多態(tài)機制。既是一種在運行時動態(tài)確定語義的機制。
1.1 什么是函數指針?
函數指針就是一個指針,也就是一個指向函數地址的變量。你必須注意的是,一個正在運行的程序在主內存內獲得一段固定的空間,并且編譯出來的可執(zhí)行程序代碼和代碼中的變量都駐留在這段內存里。而在這個程序代碼里的一個函數無非就是一個地址。重要的是你只要知道或者說你的編譯器/處理器,怎么來解釋一個指針指向的那段內存中的內容。
1.2 開場例子和怎么來代替一個Switch-語句
當你想要在程序中的某一個地方調用函數DoIt()的時候, 你只需要在源代碼的這個地方放上函數DoIt()的調用即可.那么,在編譯完這段代碼后當你的程序執(zhí)行到這個地方時這個DoIt()函數就會被調用.好像看上來一切都ok.但是,如果你不知道在代碼的構建時期(build-time)哪一個函數將要被調用的話你能做什么呢?在運行期你要是決定哪一個函數要被調用的話那你需要做什么呢?這時你可能會使用一個回調函數(Callback-Function)或者你想在一堆函數列表中選擇其中的一個。然而,你也能使用switch語句,在想要調用函數的地方使用不同的分支來解決這個問題。但是,這里我們講述的是另一個方式:就是使用函數指針!
在下面的例子中,我們能看到它的主要處理就是執(zhí)行一個算術操作(共有4個算術操作)。首先是使用了switch語句來實現(xiàn),另外還使用了函數指針處理這個調用。這僅僅是個處理簡單的例子,我想可能正常情況下沒有人會使用函數指針來這么作吧;-)
//------------------------------------------------------------------------------------
// 1.2 開場例子和怎么替代一個Switch-語句
// 任務:通過字符'+', '-', '*' 和 '/'
// 選擇執(zhí)行一個基本的算術操作.
// 四個算術操作
使用swicth或者一個函數指針
// 在運行期選擇這些函數中的一個
float Plus (float a, float b) { return a+b; }
float Minus (float a, float b) { return a-b; }
float Multiply(float a, float b) { return a*b; }
float Divide (float a, float b) { return a/b; }
// switch-語句的解決方案 - <opCode> 要選擇那一個函數的操作碼
void Switch(float a, float b, char opCode)
{
float result;
// 執(zhí)行操作
switch(opCode)
{
case '+' : result = Plus (a, b); break;
case '-' : result = Minus (a, b); break;
case '*' : result = Multiply (a, b); break;
case '/' : result = Divide (a, b); break;
}
cout << "Switch: 2+5=" << result << endl; // 顯示結果
}
// 函數指針的解決方案 - <pt2Func> 是一個指向帶有兩個float型參數
// 和float型返回值的函數. 這個函數指針“指定”了那一個函數將被執(zhí)行.
void Switch_With_Function_Pointer(float a, float b, float (*pt2Func)(float, float))
{
float result = pt2Func(a, b); // 調用函數指針
cout << "Switch replaced by function pointer: 2-5="; // 顯示結果
cout << result << endl;
}
// 執(zhí)行樣例代碼
void Replace_A_Switch()
{
cout << endl << "Executing function 'Replace_A_Switch'" << endl;
Switch(2, 5, /* '+' 指定了 'Plus'函數將被執(zhí)行 */ '+');
Switch_With_Function_Pointer(2, 5, /* 指向'Minus'函數的指針 */ &Minus);
}
提示:一個函數指針總是使用一個特定的標識來指向一個函數!然而,一旦你想要用一個函數指針指向很多函數,那的保證這些函數擁有相同的參數和返回值。
導引
|
1. 函數指針簡介
|
2. C 和 C++ 函數指針語法
|
3. 在C 和 C++里怎么實現(xiàn)回調函數 ?
|
4. 封裝C 和 C++函數指針的仿函數
|
5. 相關鏈接
|
2. C 和 C++ 函數指針語法
2.1 定義函數指針
在語法上, 函數指針有兩種不同的類型: 一種是指向普通函數或靜態(tài)C++成員函數的指針. 另一種是指向非靜態(tài)的C++成員函數. 它們之間基本的區(qū)別就是所有指向非靜態(tài)成員函數的函數指針需要一個隱含參數: 成員函數所屬類的實例. 經常要注意的是: 這兩種類型之間的函數指針是相互不兼容的.
因為函數指針無非就是一個變量, 所以它的定義也跟正常變量一樣定義. 在下面的例子里,我們定義了3個函數指針,分別是pt2Function, pt2Member 和 pt2ConstMember. 它們指向的函數的參數(一個float和兩個char)和返回值都相同. 在C++ 的例子里,指向的函數都是必須是類TMyClass的成員函數.
// 2.1 定義一個函數指針并初始化為NULL
int (*pt2Function)(float, char, char) = NULL; // C
int (TMyClass::*pt2Member)(float, char, char) = NULL; // C++
int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL; // C++
2.2 調用規(guī)約
一般的情況下你不用考慮一個函數的調用規(guī)約(calling convention): 如果你沒有指定另外的規(guī)約的話編譯器是把__cdecl 作為默認的規(guī)約. 如果你想要了解的更多的話, 那就繼續(xù)往后讀吧... 我們說的這個調用規(guī)約就是告訴編譯器做一些時而,比如怎么去傳遞參數或者怎么去生成函數的名字. 某些例子里還有其它的調用規(guī)約如__stdcall, __pascal 和 __fastcall. 這些調用規(guī)約實際上是屬于函數的標識(signature): 那么函數與和自己有些不同調用規(guī)約的函數指針之間是相互不兼容的! 對于Borland 和Microsoft 的編譯器規(guī)定你要指定的調用規(guī)約應該放在返回值和函數或函數名之間. 而對于GNU GCC 的編譯器是使用 __attribute__ 關鍵字: 也就是通過關鍵字__attribute__把調用規(guī)約寫在函數定義的后面并用雙括號把它括上. 如果有誰還知道更多的調用規(guī)約的話: 請讓我知道;-) 另外,你想要知道函數調用在編譯規(guī)約下是怎么工作的,請閱讀Paul Carter的 PC Assembly Tutorial Subprograms 這一章節(jié).
// 2.2 定義一個調用規(guī)約
void __cdecl DoIt(float a, char b, char c); // Borland and Microsoft
void DoIt(float a, char b, char c) __attribute__((cdecl)); // GNU GCC
2.3 給函數指針賦值
把一個函數的地址賦給一個函數指針是非常容易. 你只要知道函數和成員函數的名字. 盡管大多數編譯器需要在函數名的前面加上一個地址符& 但為了代碼的可移植行你應該這么做. 當指向成員函數的時候你還需要在該函數前面加上類名和域操作符(::). 你還要保證, 在你賦值的地方已經被允許訪問該函數.
// 2.3 給函數指針賦值
// 注意: 盡管你能刪掉這個地址符也能在大多數的編譯器編譯通過
// 但是為了提高程序的移植性你應該使用這個正確的方法.
// C
int DoIt (float a, char b, char c){ printf("DoIt\n"); return a+b+c; }
int DoMore(float a, char b, char c)const{ printf("DoMore\n"); return a-b+c; }
pt2Function = DoIt; // 短格式
pt2Function = &DoMore; // 使用地址符的正確賦值方法
// C++
class TMyClass
{
public:
int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
int DoMore(float a, char b, char c) const
{ cout << "TMyClass::DoMore" << endl; return a-b+c; };
/* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoMore; //使用地址符的正確賦值方法
pt2Member = &TMyClass::DoIt; // 注意: <pt2Member> 也可以指向 &DoMore函數
2.4 比較函數指針
對于函數指針你也能像正常寫法一樣使用比較操作符(==, !=). 在下面的例子里,檢查pt2Function 和pt2Member 是否是真的等于函數DoIt 和 TMyClass::DoMore 的地址. 相當的時候輸出一段字符串.
// 2.4 比較函數指針
// C
if(pt2Function >0){ // 檢查是否被初始化
if(pt2Function == &DoIt)
printf("Pointer points to DoIt\n"); }
else
printf("Pointer not initialized!!\n");
// C++
if(pt2ConstMember == &TMyClass::DoMore)
cout << "Pointer points to TMyClass::DoMore" << endl;
2.5 通過函數指針調用函數
在C語言里你能顯示地使用解引用操作符*來調用一個函數. 還可以直接使用函數指針來代替你要調用函數的名字. 在 C++ 里面.* 和 ->* 這兩個操作符可以分別與類事例在一起使用來調用這些類的成員函數(非靜態(tài)). 如果這個調用發(fā)生這些類的成員函數里則可以使用this-指針.
// 2.5 使用函數指針調用函數
int result1 = pt2Function (12, 'a', 'b'); // C偷懶格式
int result2 = (*pt2Function) (12, 'a', 'b'); // C
TMyClass instance1;
int result3 = (instance1.*pt2Member)(12, 'a', 'b'); // C++
int result4 = (*this.*pt2Member)(12, 'a', 'b'); // C++ 如果this指針能被使用
TMyClass* instance2 = new TMyClass;
int result4 = (instance2->*pt2Member)(12, 'a', 'b'); // C++, instance2 是一個指針
delete instance2;
2.6 怎么把一個函數指針作為參數進行傳遞?
你能把一個函數指針作為一個函數的調用參數. 下列代碼顯示了怎么傳遞一個函數指針(返回值為int型,第一個參數為float型,第二,三個參數都為char型):
//------------------------------------------------------------------------------------
// 2.6 怎么傳遞一個函數指針
// <pt2Func> 是一個指向帶有一個float型和兩個int型參數以及返回值是int型的函數
void PassPtr(int (*pt2Func)(float, char, char))
{
int result = (*pt2Func)(12, 'a', 'b'); // 調用函數指針
cout << result << endl;
}
// 執(zhí)行樣例代碼 - 'DoIt' 是在上面2.1-4定義個函數
void Pass_A_Function_Pointer()
{
cout << endl << "Executing 'Pass_A_Function_Pointer'" << endl;
PassPtr(&DoIt);
}
2.7 怎么返回函數指針 ?
把一個函數指針作為返回值需要一個小技巧. 就像下面的例子一樣有兩種方法來返回一個帶有兩個參數和返回值的函數指針. 如果你想要返回一個指向成員函數的指針你只需改一下函數指針的定義/聲明.
//---------------------------------------------------------------------------
// 2.7 怎么返回一個函數指針
// 'Plus' 和'Minus'參看前面的定義. 它們都返回一個float 和 帶有兩個float參數
// 直接方案: 定義了一個帶有char型參數并且返回一個指向帶有兩個float型和返回值為float
// 型的函數. <opCode>則是決定哪個函數被返回
float (*GetPtr1(const char opCode))(float, float)
{
if(opCode == '+')
return &Plus;
else
return &Minus; // 如果傳遞的參數為無效時,是缺省函數
}
// 使用typedef的方案: 定義一個指向帶有兩個floats型和返回值是float型的函數
typedef float(*pt2Func)(float, float);
// 定義帶有一個char型參數和返回一個上面定一個的函數指針的函數
// <opCode> 決定那一個函數被返回
pt2Func GetPtr2(const char opCode)
{
if(opCode == '+')
return &Plus;
else
return &Minus; //如果傳遞的參數為無效時,是缺省函數
}
// 執(zhí)行樣例代碼
void Return_A_Function_Pointer()
{
cout << endl << "Executing 'Return_A_Function_Pointer'" << endl;
// 定義函數指針并初始化為NULL
float (*pt2Function)(float, float) = NULL;
pt2Function=GetPtr1('+'); // 通過函數指針'GetPtr1'得到調用函數
cout << (*pt2Function)(2, 4) << endl; // 調用該函數
pt2Function=GetPtr2('-'); //通過函數指針'GetPtr2'得到調用函數
cout << (*pt2Function)(2, 4) << endl; //調用該函數
}
2.8 怎么使用函數指針數組?
操作函數指針數組是非常有意思的事情. 這使得用一個索引來選擇一個函數指針變得可能. 這個語法表示起來較困難,常常導致混淆. 下面有兩種方法可以在C 和C++里定義并使用一個函數指針數組. 第一個方法是使用typedef, 第二個方法是直接定一個數組. 愿意使用那種方法完全取決于你.
//--------------------------------------------------------------------------- 2.8 怎么使用函數指針數組
// C -----------------------------------------------------------------------
// 類型定義: 'pt2Function' 現(xiàn)在能被作為一個類型來使用
typedef int (*pt2Function)(float, char, char);
// 闡述一個函數指針數組是怎么工作的
void Array_Of_Function_Pointers()
{
printf("\nExecuting 'Array_Of_Function_Pointers'\n");
// 定義一個數組并初始化每一個元素為NULL, <funcArr1> 和 <funcArr2> 是帶有
// 10個函數指針的數組
// 第一個方法是使用 typedef
pt2Function funcArr1[10] = {NULL};
// 第二個方法是直接定義這個數組
int (*funcArr2[10])(float, char, char) = {NULL};
// 賦于函數的地址 - 'DoIt' 和 'DoMore' 的定義請參照上面2.1-4
funcArr1[0] = funcArr2[1] = &DoIt;
funcArr1[1] = funcArr2[0] = &DoMore;
/* 更多的賦值 */
// 使用一個索引來調用這些函數指針
printf("%d\n", funcArr1[1](12, 'a', 'b')); // 偷懶格式
printf("%d\n", (*funcArr1[0])(12, 'a', 'b')); // "正確" 的調用方式
printf("%d\n", (*funcArr2[1])(56, 'a', 'b'));
printf("%d\n", (*funcArr2[0])(34, 'a', 'b'));
}
// C++ -------------------------------------------------------------------
// 類型定義: 'pt2Member' 現(xiàn)在能被作為類型來使用
typedef int (TMyClass::*pt2Member)(float, char, char);
// 闡述成員函數指針是怎么工作的
void Array_Of_Member_Function_Pointers()
{
cout << endl << "Executing 'Array_Of_Member_Function_Pointers'" << endl;
// 定義一個數組并初始化每一個元素為NULL, <funcArr1> 和 <funcArr2> 是帶有
// 10個函數指針的數組
// 第一個方法是使用 typedef
pt2Member funcArr1[10] = {NULL};
// 第二個方法是直接定義這個數組
int (TMyClass::*funcArr2[10])(float, char, char) = {NULL};
// 賦于函數的地址 - 'DoIt' 和 'DoMore' 的定義請參照上面2.1-4
funcArr1[0] = funcArr2[1] = &TMyClass::DoIt;
funcArr1[1] = funcArr2[0] = &TMyClass::DoMore;
/* 更多的賦值 */
// 使用一個索引來調用這些成員函數指針
// 注意: 要調用成員函數則需要一個TMyClass類的實例
TMyClass instance;
cout << (instance.*funcArr1[1])(12, 'a', 'b') << endl;
cout << (instance.*funcArr1[0])(12, 'a', 'b') << endl;
cout << (instance.*funcArr2[1])(34, 'a', 'b') << endl;
cout << (instance.*funcArr2[0])(89, 'a', 'b') << endl;
}
導引
|
1. 函數指針簡介
|
2. C 和 C++ 函數指針語法
|
3. 在C 和 C++里怎么實現(xiàn)回調函數 ?
|
4. 封裝C 和 C++函數指針的仿函數
|
5. 相關鏈接
|
3. 怎么在 C 和 C++中實現(xiàn)回調
3.1 回調函數的概念
在回調函數的概念中當然少不了函數指針這東西. 如果你不知道怎么使用函數指針的話你可以去看一下函數指針簡介 這一章. 我將使用著名的排序函數qsort來給大家解釋回調函數的概念. 這個函數可以根據用戶指定的排序方式來排列一個區(qū)域中的很多項目之間的順序. 這個區(qū)域中包含的項目可以是任何類型; 它是通過void-類型的指針被傳遞到這個排序函數里. 另外該類型項目的大小和這個區(qū)域中項目的數量也要被傳遞到排序函數里.現(xiàn)在的問題是: 這個排序函數在不知道項目類型的任何信息的情況下怎么實現(xiàn)排序功能的呢? 這個答案非常簡單: 就是這個函數要接收一個指向帶有兩個參數是void型項目指針的比較函數的函數指針, 這個比較函數則是來估算兩個項目之間的順序并返回一個int型的結果代碼. 因此每一次這個排序算法需要決定兩個項目之間的順序時則僅僅是通過函數指針來調用這個比較函數即可: 這就是回調!
3.2 怎么在C里實現(xiàn)回調?
下面是函數 qsort 的聲明:
void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
int(_USERENTRY *cmpFunc)(const void*, const void*));
field 指向要被排序的那個域中的第一個元素, nElements 是這個域里的項目數量, sizeOfAnElement 則是用字節(jié)表示的一個項目的大小并且 cmpFunc 是指向比較函數的函數指針. 這個比較函數帶有兩個void-型指針的參數并返回一個型的返回值int. 在函數的定義里怎么把函數指針作為一個參數來使用的語法看起來有一些陌生. 只要看看, 怎么定義一個函數指針這一章你就能發(fā)現(xiàn)它完全就是相同的. 執(zhí)行一個 回調就像普通函數被調用那樣簡單: 你只需要使用函數指針的名字而不是那個函數的名字. 就像下面顯示的那樣. 注意: 下面的代碼中除了函數指針所有的參數都被省略了因為我們只是關注跟函數指針相關的事情.
void qsort(
, int(_USERENTRY *cmpFunc)(const void*, const void*))
{
/* 排序算法 - 注意: item1 和 item2 是 void-型的指針 */
int bigger=cmpFunc(item1, item2); // 做一個回調
/* 使用上面的結果 */
}
3.3 qsort的使用例子
在下面的例子里排序 floats 型的項目.
//-----------------------------------------------------------------------------------------
// 3.3 通過qsort排序函數的方法在C 中實現(xiàn)回調
#include <stdlib.h> // due to: qsort
#include <time.h> // randomize
#include <stdio.h> // printf
// 排序算法的比較函數
// 兩個被比較的項目的類型都是 void-指針, 先作轉換后作比較
int CmpFunc(const void* _a, const void* _b)
{
// you've got to explicitly cast to the correct type
const float* a = (const float*) _a;
const float* b = (const float*) _b;
if(*a > *b) return 1; // first item is bigger than the second one -> return 1
else
if(*a == *b) return 0; // equality -> return 0
else return -1; // second item is bigger than the first one -> return -1
}
// 使用qsort()的例子
void QSortExample()
{
float field[100];
::randomize(); // 初始化隨機數生成器
for(int c=0;c<100;c++) // 給域中的每個元素設定隨機值
field[c]=random(99);
// 用 qsort()進行排序
qsort((void*) field, /*項目的數量*/ 100, /*一個項目的大小*/ sizeof(field[0]),
/*比較函數*/ CmpFunc);
// 排完序后顯示前10個元素
printf("The first ten elements of the sorted field are
\n");
for(int c=0;c<10;c++)
printf("element #%d contains %.0f\n", c+1, field[c]);
printf("\n");
}
3.4 實現(xiàn) C++ 靜態(tài)成員函數的回調?
這跟在C里的實現(xiàn)是完全一樣的. 靜態(tài)成員函數不需要調用類對象并就像C函數一樣擁有相同標識,相同的調用規(guī)約,調用參數以及返回值.
3.5 實現(xiàn) C++ 非靜態(tài)成員函數的回調?
封裝方法
指向非靜態(tài)成員的指針和普通的C指針是不一樣的,因為它們需要傳遞一個類對象的this-指針. 而且普通的函數指針和非靜態(tài)成員函數有些不同并存在不兼容的標識(signatures)! 如果你想要回調一個指定類的成員函數那你得把你的代碼從普通的函數指針改成一個指向成員函數的指針. 但是如果你想要回調一個任意類的非靜態(tài)成員函數那你能怎么做呢? 這有一點兒困難. 你需要寫一個靜態(tài)的成員函數作為包裝函數. 一個靜態(tài)成員函數擁有和C函數一樣的標識! 然后你要把要調用的成員函數的類對象指針強轉為void* 并作為附加參數或者全局變量傳遞到包裝函數里. 有一點比較重要,如果你使用全局變量時你必須得保證這個指針總是指向一個正確的類對象! 當然你也得為成員函數傳遞那些調用參數. 這個包裝函數把void指針強轉為對應類的實例的指針并調用這個成員函數. 在后面你能看到兩個例子.
例 A: 作為附加參數來傳遞類實例的指針
函數 DoItA 利用TClassA類(包含有一個回調)的對象作一些事情. 因此指向靜態(tài)包裝函數TClassA::Wrapper_To_Call_Display的指針要被傳遞到函數DoItA中. 這個包裝函數是一個回調函數. 你也能隨便寫一個類似于TClassA的類并在函數DoItA中使用當然前提是那些類立提供了包裝函數. 注意:利用回調函數作為接口的設計方案會比下面使用全局變量的那個方案要有用的多.
//-----------------------------------------------------------------------------------------
// 3.5 例A: 使用附加參數來回調成員函數
// 任務: 函數 'DoItA'中回調成員函數'Display'. 因此要使用包裝函數
// 'Wrapper_To_Call_Display'.
#include <iostream.h> // due to: cout
class TClassA
{
public:
void Display(const char* text) { cout << text << endl; };
static void Wrapper_To_Call_Display(void* pt2Object, char* text);
/* more of TClassA */
};
// 靜態(tài)成員函數能回調成員函數Display()
void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string)
{
// 顯示地強轉為TClassA類的指針
TClassA* mySelf = (TClassA*) pt2Object;
// 調用成員函數
mySelf->Display(string);
}
// 隱含地做回調
// 注意: 當然這個函數也能作為成員函數
void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text))
{
/* do something */
pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)"); // make callback
}
// 執(zhí)行例程
void Callback_Using_Argument()
{
// 1. TClassA類對象的初始化
TClassA objA;
// 2. 為對象<objA>調用函數'DoItA'
DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
}
例B: 把類實例的指針作為全局變量
函數DoItB利用TClassA類(包含有一個回調)的對象作一些事情. 因此指向靜態(tài)包裝函數TClassB::Wrapper_To_Call_Display的指針要被傳遞到函數DoItA中. 這個包裝函數是一個回調函數. 你也能隨便寫一個類似于TClassA的類并在函數DoItA中使用當然前提是那些類立提供了包裝函數. 注意: 這個方案在已經存在的回調接口不會被改變的前提下才會比較有用,但因為使用全局變量可能會非常危險而且還可能到使嚴重錯誤所以這并不是一個很好的方案.
//-----------------------------------------------------------------------------------------
// 3.5 例B: 使用全局變量回調成員函數
// 任務: 函數 'DoItB'中回調成員函數'Display'. 因此要使用包裝函數
// 'Wrapper_To_Call_Display'..
#include <iostream.h> // due to: cout
void* pt2Object; // 一個可以指向任意類的全局變量
class TClassB
{
public:
void Display(const char* text) { cout << text << endl; };
static void Wrapper_To_Call_Display(char* text);
/* more of TClassB */
};
// 靜態(tài)成員函數能回調成員函數 Display()
void TClassB::Wrapper_To_Call_Display(char* string)
{
// 顯示地將全局變量 <pt2Object> 強轉成類TClassB的指針
// 警告: <pt2Object> 必須指向一個有效的類對象!
TClassB* mySelf = (TClassB*) pt2Object;
// call member
mySelf->Display(string);
}
// 隱含地做回調
// 注意: 當然這個函數也能作為成員函數
void DoItB(void (*pt2Function)(char* text))
{
/* do something */
pt2Function("hi, i'm calling back using a global ;-)"); // make callback
}
// execute example code
void Callback_Using_Global()
{
// 1. TClassB類對象的初始化
TClassB objB;
// 2. 對在靜態(tài)包裝函數中要使用的全局變量進行賦值
// 重要: 一定不要忘記作這一步
pt2Object = (void*) &objB;
// 3. call 'DoItB' for <objB>
DoItB(TClassB::Wrapper_To_Call_Display);
}
導引
|
1. 函數指針簡介
|
2. C 和 C++ 函數指針語法
|
3. 在C 和 C++里怎么實現(xiàn)回調函數 ?
|
4. 封裝C 和 C++函數指針的仿函數
|
5. 相關鏈接
|
4. 封裝C 和 C++函數指針的仿函數
4.1 什么是仿函數 ?
仿函數是一個具有狀態(tài)的函數. 在C++ 你能看到它們作為一個用一到兩個私有成員來存儲這些狀態(tài)的類來出現(xiàn),并利用重載的操作符()來執(zhí)行本身的操作. 仿函數是利用模版(templates)和多態(tài)(polymorphism)的概念來封裝C 和 C++ 的函數指針. 你能構建一個任意類的成員函數的指針列表并通過相同的接口來調用它們,但不會干擾它們的類或者一個實例的指針的需求. 僅僅是要求所有的函數要保持相同的返回類型和調用參數. 有時候仿函數通常會被稱為閉包(closures). 你也能用仿函數來實現(xiàn)回調.
4.2 怎么實現(xiàn)仿函數 ?
首先你需要一個提供了一個叫Call的虛函數的基類TFunctor或者一個能調用成員函數的虛擬重載操作符 ().是選擇重載操作符還是函數Call當然是隨便你自己的喜好了. 從這個基類你能派生一個模版類 TSpecificFunctor ,在構造函數里用一個對象的指針和一個函數指針來初始化.這個派生類重載基類中的函數Call并/或操作符(): 在這個重載的版本里,你能利用存儲的函數指針和類對象的指針來調用成員函數. 如果你忘了怎么去使用函數指針你可以參考章節(jié)函數指針簡介.
//-----------------------------------------------------------------------------------------
// 4.2 怎么實現(xiàn)仿函數
// 抽象基類
class TFunctor
{
public:
// 兩個用來調用成員函數的虛函數
// 派生類將使用函數指針和類對象指針來實現(xiàn)函數調用
virtual void operator()(const char* string)=0; // call using operator
virtual void Call(const char* string)=0; // call using function
};
// 派生模版類
template <class TClass> class TSpecificFunctor : public TFunctor
{
private:
void (TClass::*fpt)(const char*); // pointer to member function
TClass* pt2Object; // pointer to object
public:
// 構造函數- 把類對象指針和函數指針
// 存儲在兩個私有變量中
TSpecificFunctor(TClass* _pt2Object, void(TClass::*_fpt)(const char*))
{ pt2Object = _pt2Object; fpt=_fpt; };
// 重載操作符 "()"
virtual void operator()(const char* string)
{ (*pt2Object.*fpt)(string);}; // execute member function
// 重載函數 "Call"
virtual void Call(const char* string)
{ (*pt2Object.*fpt)(string);}; // execute member function
};
4.3 仿函數的使用列
在下面的例子里我們有兩個類,包括返回值是(void)和參數為(const char*)的函數Display的一個簡單實現(xiàn).這里我們創(chuàng)建兩個TFunctor類的指針數組并且用封裝了類對象指針和TClassA ,TClassB的成員函數指針的TSpecificFunctor類來初始化這個數組中的元素. 而后我們使用仿函數數組來分別調用這兩個成員函數. 類對象并不直接調用函數但你得保證你的操作不能干擾這兩個類的操作!
//-----------------------------------------------------------------------------------------
// 4.3 仿函數的使用例子
// 類A
class TClassA{
public:
TClassA(){};
void Display(const char* text) { cout << text << endl; };
/* more of TClassA */
};
// 類 B
class TClassB{
public:
TClassB(){};
void Display(const char* text) { cout << text << endl; };
/* more of TClassB */
};
// 主程序
int main(int /*argc*/, char* /*argv[]*/)
{
// 1. TClassA 和TClassB的實例
TClassA objA;
TClassB objB;
// 2. 實例化TSpecificFunctor 對象 
// a ) 封裝指向TClassA類成員的函數指針的仿函數
TSpecificFunctor<TClassA> specFuncA(&objA, &TClassA::Display);
// b) 封裝指向TClassB類成員的函數指針的仿函數
TSpecificFunctor<TClassB> specFuncB(&objB, &TClassB::Display);
// 3. 聲明基類TFunctor指針的數組, 并初始化
TFunctor* vTable[] = { &specFuncA, &specFuncB };
// 4. 不需要類對象就可以調用成員函數
vTable[0]->Call("TClassA::Display called!"); // via function "Call"
(*vTable[1]) ("TClassB::Display called!"); // via operator "()"
// hit enter to terminate
cout << endl << "Hit Enter to terminate!" << endl;
cin.get();
return 0;
}
導引
|
1. 函數指針簡介
|
2. C 和 C++ 函數指針語法
|
3. 在C 和 C++里怎么實現(xiàn)回調函數 ?
|
4. 封裝C 和 C++函數指針的仿函數
|
5. 相關鏈接
|
5.相關鏈接
5.1 介紹函數指針
5.2 回調和仿函數
5.2 其他項