指向類成員函式的指標並非指標
參考<<C++必知必會>>的相關章節
"指向類成員函式的指標",這個術語中包含了"類成員函式"的術語,但是嚴格的說,這裡的成員函式只是指非靜態成員函式,這個術語中還包含了"指標"這個術語,但是嚴格的說,它即不包含地址,行為也不象指標,說得乾脆點,那就是"指向類成員函式的指標"並非指標.儘管這個術語有很大的迷惑性,但是就其含義來說,可以把一組同類型的函式抽象為一個"指向函式的指標",同樣的道理,可以把一組類中同類型的類成員函式抽象為一個"指向類成員函式的指標",兩者是一致的"指向類成員函式的指標"和"指向函式的指標"有什麼區別?從字面上就可以清楚的知道,前者是和類,物件相關的,而後者直接指向函式的地址我們首先複習一下"指向函式的指標"如何使用 ?
void print()
{
}
void (*pfun)(); //宣告一個指向函式的指標,函式的引數是 void,函式的返回值是 void pfun = print; //賦值一個指向函式的指標
(*pfun)(); //使用一個指向函式的指標比較簡單,不是嗎?為什麼*pfun 需要用()擴起來呢?因為*的運算子優先順序比()低,如果不用 ()就成了 *(pfun())
struct CPoint
{
void plus(double x_, double y_){}
void minus(double x_, double y_){}
void mul(double x_, double y_){}
void dev(double x_, double y_){}
virtual void move(double x_, double y_){}
double x;
double y;
void Oper(CPoint* pPoint, void (CPoint::*pfun)(double x_, double y_), int x, int y)
{
(pPoint->*pfun)(x, y);}
struct CPoint3d : public CPoint
{
void move(double x_, double y_){ }};
int main(int argc, char* argv[])
{
CPoint pt;
void (CPoint::*pfun)(double x_, double y_);
int offset = 0;
pfun = &CPoint::plus;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
pfun = &CPoint::minus;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
pfun = &CPoint::move;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
CPoint3d pt3d;
void (CPoint3d::*p3dfun)(double x_, double y_);
p3dfun = &CPoint3d::move;
(pt3d.*p3dfun)(10, 10);
//p3dfun = pfun; //正確
//pfun = p3dfun; //錯誤
pfun = (void (CPoint::*)(double, double))p3dfun;
Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
return 0;
void (CPoint::*pfun)(double x_, double y_);
這裡是"指向類成員函式的指標"的宣告,就是多了 CPoint::的限定
pfun = &CPoint::plus;
這裡是"指向類成員函式的指標"的賦值,在賦值的時候必須用這種靜態的方式
(pt.*pfun)(10, 10);
這裡是"指向類成員函式的指標"的使用 ,記住,解引用必須有實際的 this 指標地址,因此必須用有地址的物件 pt 來解引用 ,.*的語法有些怪異,不過我寧願把它拆解為 pt.和*pfun 兩部分來理解
offset = (int&)pfun;
這裡 offset=4198410,當然不同的專案 ,不同的編譯器這個值是不同的,由此也可以知道,"指向類成員函式的指標"確實是一個指標,其實由 C++物件模型我們就應該知道這個結論了
,在 C++物件模型中,成員函式是全域性的,並不屬於物件有人想用這個值嗎?或許可以用下面的程式碼:void (CPoint::*pfun2)(double x_, double y_);
memcpy(&pfun2, &offset, sizeof(int));
Oper(&pt, pfun2, 10, 10);
不過,我還是忍不住奉勸各位,儘量不要直接使用這個值,這畢竟是編譯器內部實現的細節,實在有太多的人喜歡這種黑客似的程式碼並四處炫耀,真正的"指向類成員函式的指標"的用法只應該包括宣告 ,賦值和解引用
pfun = &CPoint::move;
注意到這裡的 move 是虛擬函式,那麼這裡還支援虛擬函式的多型嗎?沒有問題,"指向類成員函式的指標"支援多型,當然了 ,代價是,這時候這個指標就必須擴充套件為一個結構了 ,C++為了"指向類成員函式的指標"支援多型是要付出代價的
p3dfun = pfun; //正確
存在基類的"指向類成員函式的指標"到派生類的"指向類成員函式的指標"的隱式轉換,其含義無疑是說基類的成員函式佈局資訊只是派生類中成員函式佈局資訊的一個子集,因此這樣的轉換應該是沒有問題的,但是反過來呢?
//pfun = p3dfun; //錯誤
不存在派生類的"指向類成員函式的指標"到基類的"指向類成員函式的指標"的隱式轉換,因為派生類中的成員函式並不一定能夠在基類中找到"指向類成員函式的指標"基類和派生類的關係和"指向類物件的指標"基類和派生類的關係完全相反,就"指向類成員函式的指標"的本質來說,這是合理的,但是這樣的話,我們就無法利用公共的 Oper 函數了 ,除非...
pfun = (void (CPoint::*)(double, double))p3dfun; //強制轉換
我們做強制轉換是可以的
Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
而且也只有強制轉換才可以利用公共的 Oper 函數了 ,這裡的 Oper 呼叫的是 pt3d 中的 move
函式,沒有錯誤的但是是否一定要這樣做呢?這取決於程式設計師自己的選擇