我們首先復習一下"指向函數的指針"如何使用?
- void print()
- {
- }
- void (*pfun)(); //聲明一個指向函數的指針,函數的參數是 void,函數的返回值是 void
- pfun = print; //賦值一個指向函數的指針
- (*pfun)(); //使用一個指向函數的指針

比較簡單,不是嗎?為什么*pfun需要用()擴起來呢?

因為*的運算符優先級比()低,如果不用()就成了*(pfun()).

指向類的成員函數的指針不過多了一個類的限定而已!
- class A
- {
- void speak(char *, const char *);
- };
- void main()
- {
- A a;
- void (A::*pmf)(char *, const char *);//指針的聲明
- pmf = &A::speak; //指針的賦值
- }

一個指向類A 成員函數的指針聲明為:

void (A::*pmf)(char *, const char *);

聲明的解釋是:pmf是一個指向A成員函數的指針,返回無類型值,函數帶有二個參數,參數的類型分別是char *和const char *。除了在星號前增加A::,與聲明外部函數指針的方法一樣。一種更加高明的方法是使用類型定義:例如,下面的語句定義了PMA是一個指向類A成成員函數的指針,函數返回無類型值,函數參數類型為char *和const char *:

typedef void(A::*PMA)(char *,const char *);

PMA pmf= &A::strcat;//pmf是 PMF類型(類A成員指針)的變量

下面請看關於指向類的成員函數的使用示例:
- #include <iostream>
- using namespace std;
- class Person
- {
- public:
- /*這里稍稍注意一下,我將speak()函數設置為普通的成員函數,而hello()函數設置為虛函數*/
- int value;
- void speak()
- {
- cout << "I am a person!" << endl;
- printf ("%d\n", &Person::speak); /*在這里驗證一下,輸出一下地址就知道了!*/
- }
- virtual void hello()
- {
- cout << "Person say \"Hello\"" << endl;
- }
- Person()
- {
- value = 1;
- }
- };
- class Baizhantang: public Person
- {
- public:
- void speak()
- {
- cout << "I am 白展堂!" << endl;
- }
- virtual void hello()
- {
- cout << "白展堂 say \"hello!\"" << endl;
- }
- Baizhantang()
- {
- value = 2;
- }
- };
- typedef void (Person::*p)();//定義指向Person類無參數無返回值的成員函數的指針
- typedef void (Baizhantang::*q)();//定義指向Baizhantang類的無參數無返回值的指針
- int main()
- {
- Person pe;
- int i = 1;
- p ip;
- ip = &Person::speak; //ip指向Person類speak函數
- (pe.*ip)(); //這個是正確的寫法!
- //--------------------------------------------
- // result : I am a Person!
- // XXXXXXXXXX(表示一段地址)
- //--------------------------------------------
- /*
- *下面是幾種錯誤的寫法,要注意!
- * pe.*ip();
- * pe.(*ip)();
- * (pe.(*ip))();
- */
- Baizhantang bzt;
- q iq = (void (Baizhantang::*)())ip; //強制轉換
- (bzt.*iq)();
- //--------------------------------------------
- // result : I am a Person!
- // XXXXXXXXXX(表示一段地址)
- //--------------------------------------------
- /* 有人可能會問了:ip明明被強制轉換成了Baizhantang類的成員函數的指針,為什么輸出結果還是:
- * I am a Person!在C++里面,類的非虛函數都是采用靜態綁定,也就是說類的非虛函數在編譯前就已經
- *確定了函數地址!ip之前就是指向Person::speak函數的地址,強制轉換之后,只是指針類型變了,里面
- *的值並沒有改變,所以調用的還是Person.speak函數,細心的家伙會發現,輸出的地址都是一致的.
- *這里要強調一下:對於類的非靜態成員函數,c++編譯器會給每個函數的參數添加上一個該類的指針this,這也
- *就是為什么我們在非靜態類成員函數里面可以使用this指針的原因,當然,這個過程你看不見!而對於靜態成員
- *函數,編譯器不會添加這樣一個this。
- */
- iq = &Baizhantang::speak; /*iq指向了Baizhantang類的speak函數*/
- ip = (void (Person::*)())iq; /*ip接收強制轉換之后的iq指針*/
- (bzt.*ip)();
- //--------------------------------------------
- // result : I am 白展堂!
- //--------------------------------------------
- (bzt.*iq)();//這里我強調一下,使用了動態聯編,也就是說函數在運行是才確定函數地址!
- //--------------------------------------------
- // result : I am 白展堂!
- //--------------------------------------------
- /*這一部分就沒有什么好講的了,很明白了!由於speak函數是普通的成員函數,在編譯時就知道
- *到了Baizhantang::speak的地址,因此(bzt.*ip)()會輸出“I am 白展堂!”,即使iq被強制轉換
- *成(void (Person::*)())類型的ip,但是其值亦未改變,(bzt.*iq)()依然調用iq指向處的函數
- *即Baizhantang::speak.
- */
- /*好了,上面講完了普通成員函數,我們現在來玩一點好玩的,現在來聊虛函數*/
- ip = &Person::hello; /*讓ip指向Person::hello函數*/
- (pe.*ip)();
- //--------------------------------------------
- // result : Person say "Hello"
- //--------------------------------------------
- (bzt.*ip)();
- //--------------------------------------------
- // result : 白展堂 say "Hello"
- //--------------------------------------------
- /*咦,這就奇怪了,為何與上面的調用結果不類似?為什么兩個調用結果不一致?伙伴們注意了:
- *speak函數是一個虛函數,前面說過虛函數並不是采用靜態綁定的,而是采用動態綁定,所謂動態
- *綁定,就是函數地址得等到運行的時候才確定,對於有虛函數的類,編譯器會給我們添加一個指針
- *vptr,指向一個虛函數表vptl,vptl里面存放着虛函數的地址,子類繼承父類的時候,也會繼承這樣
- *一個指針,如果子類復寫了虛函數,那么該表中該虛函數地址將會由父類的虛函數地址替換成子類虛
- *函數地址,編譯器會把(pe.*ip)()轉化成為(pe.vptr[1])(pe),加上動態綁定,結果會輸出:
- * Person say "Hello"
- *(bzt.*ip)()會被轉換成(bzt.vptr[1])(pe),自然會輸出:
- * 白展堂 say "Hello"
- *ps:這里我沒法講得更詳細,因為解釋起來肯定是很長很長的,感興趣的話,我推薦兩本書你去看一看:
- * 第一本是侯捷老師的<深入淺出MFC>,里面關於c++的虛函數特性講的比較清楚;
- * 第二本是侯捷老師翻譯的<深度探索C++對象模型>,一聽名字就知道,講這個就更詳細了;
- *當然,不感興趣的同學這段解釋可以省略,對與使用沒有影響!
- */
- iq = (void (Baizhantang::*)())ip;
- (bzt.*iq)();
- //--------------------------------------------
- // result : 白展堂 say "Hello"
- //--------------------------------------------
- system("pause");
- return 0;
- }
