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

// a ) 封裝指向TClassA類成員的函數(shù)指針的仿函數(shù)
TSpecificFunctor<TClassA> specFuncA(&objA, &TClassA::Display);
// b) 封裝指向TClassB類成員的函數(shù)指針的仿函數(shù)
TSpecificFunctor<TClassB> specFuncB(&objB, &TClassB::Display);
// 3. 聲明基類TFunctor指針的數(shù)組, 并初始化
TFunctor* vTable[] = { &specFuncA, &specFuncB };
// 4. 不需要類對(duì)象就可以調(diào)用成員函數(shù)
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;
}
|
|
|
|
|
|
5.相關(guān)鏈接
5.1 介紹函數(shù)指針
- Using Member Function Pointers 一個(gè)borland社區(qū)的詳細(xì)文章. 原本是為了給產(chǎn)品BC3.1提供支持但實(shí)際上它是平臺(tái)獨(dú)立的并且一直在維護(hù)更新. [文章]
- Classes having Pointers to Members 《C++注解》的第15章作者Frank Brokken. [教程]
- C++ FAQ-Lite 新聞組news.comp.lang.c++的FAQ. 該鏈接指向的是成員函數(shù)指針那部分. [FAQ]
- Pointing to Class Members 一個(gè)較短但很有用的文章,闡述了怎么使用成員函數(shù)指針. [教程]
- Declaring Function Pointers and Implementing Callbacks 關(guān)于C的函數(shù)指針的比較短小的文章. 里面也描述了函數(shù)標(biāo)識(shí)的調(diào)用規(guī)約. [教程]
- Notes on Function Pointers 為了闡明通用編程(generic programming)的思想的一篇簡(jiǎn)要教程: 分割算法和數(shù)據(jù). 容器類的使用函數(shù)指針的例子, 例如. 調(diào)用用戶指定的函數(shù)為每一個(gè)容器元素. [教程]
5.2 回調(diào)和仿函數(shù)
- Callbacks in C++ 一個(gè)免費(fèi)的回調(diào)庫和一篇科學(xué)且實(shí)用的回調(diào)文章,闡述了幾乎所有的回調(diào)方法. 為每一個(gè)步驟的描述中的源代碼都完全開放. [站點(diǎn)]
- Callbacks in C++ using Template Functors Rich Hickey 寫的一篇關(guān)于回調(diào)概念和模版仿函數(shù)的用法的詳細(xì)的文章. 這個(gè)站點(diǎn)已經(jīng)被關(guān)閉了,文章中的免費(fèi)C++回調(diào)庫可以再這里下載. [文章]
- Callbacks in C++ using Template Functors II 關(guān)于使用Rich Hickey的回調(diào)機(jī)制的一篇教程. 也解釋了其他的普通方案為什么不可取的原因. [教程]
- Myelin: How to write Callbacks in C++ and COM 解釋怎么使用純虛類做成回調(diào)的短文章. [文章]
- Myelin: Implementing delegates in C++ 怎么在C++中實(shí)現(xiàn)委派 ... [文章]
- Callbacks in C++: The OO Way [文章]
- C++ Callback Solution 使用模版做成成員函數(shù)的回調(diào). [文章]
- Member Function Pointers and the Fastest Possible C++ Delegates 怎么在C++中實(shí)現(xiàn)委派... [文章]
- Callbacks 關(guān)于回調(diào)和仿函數(shù)的簡(jiǎn)單說明. [教程]
- Comparison of Callback Mechanisms 討論C++中實(shí)現(xiàn)回調(diào)機(jī)制的5個(gè)可選方法的可用性和性能方面的對(duì)比. [文章]
- libsigc++ 用抽象回調(diào)連接一個(gè)類的方法,函數(shù),或函數(shù)對(duì)象的一個(gè)完全的回調(diào)庫. [函數(shù)庫]
- Library Boost/Function Doug Gregor寫得函數(shù)對(duì)象包裝器. 該文檔能在 www.boost.org 里下載到 . [函數(shù)庫]
- Library Boost/Functional Mark Rodgers寫的加強(qiáng)函數(shù)對(duì)象適配器. 該文檔能在 www.boost.org 里下載到. [函數(shù)庫]
5.2 其他項(xiàng)
- PC Assembly Tutorial 關(guān)于匯編的一篇很好的教程. 這里你能了解到一個(gè)函數(shù)調(diào)用是怎么工作的. 另外還有調(diào)用規(guī)約以及匯編和C代碼的之間的交互. [教程]
- The Virtual Function Mechanism 關(guān)于使用Microsoft's Visual C++匯編器的虛函數(shù)機(jī)制一篇簡(jiǎn)要介紹 [文章]