我們首先複習一下"指向函數的指針"如何使用?
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
void speak()
cout << "I am 白展堂!" << endl;
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!
/* 有人可能會問了: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)();//這裡我強調一下,使用了動态聯編,也就是說函數在運作是才确定函數位址!
/*這一部分就沒有什麼好講的了,很明白了!由于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"
// 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;
system("pause");
return 0;
}