天天看點

如何使用指向類的成員函數的指針(詳解!)

我們首先複習一下"指向函數的指針"如何使用?

如何使用指向類的成員函數的指針(詳解!)

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;  

}  

繼續閱讀