C++知識點隨筆(七):成員函數指針、模擬虛函數列表

成員函數指針

普通的全局函數指針定義:

typedef void (*PFUN)();

普通的函數指針可以指向返回值和參數類型都相同的全局函數,可是成員函數該怎麼辦呢?如果成員函數是static的,那麼可以正常指向,如下實例:

#include <iostream>
using namespace std;

class CAA{
public:
    static void ShowStatic(){
        cout << "CAA::ShowStatic()" << endl;
    }
    void Show(){
        cout << "CAA::Show()" << endl;
    }
};

void Show()
{
    cout << "Global::Show()" << endl;
}

typedef void (*PFUN)();

int main()
{
    PFUN pfun_1 = Show;     //這裏不用 & 也可以,因爲全局函數的函數名就是他的地址。
    (*pfun_1)();

    PFUN pfun_2 = CAA::ShowStatic;  //普通的函數指針指向成員函數的時候,這個成員函數必須是 static 的,並且這裏也不用 &,因爲加上 static 之後就相當於全局函數了。
    (*pfun_2)();

    system("pause");
    return 0;
}

我們知道普通的成員函數都有this指針,用來傳入對象的地址,因爲普通的成員函數雖然是屬於類的,不是屬於對象的,但是使用的時候必須通過對象調用。而static成員函數就沒有了this指針,所以就相當於全局函數,我們可以不經過對象,而是通過類名作用域來調用他了。
可是上面的例子中,如果我們用PFUN去指向類中的非靜態成員函數就會報錯。所以我們要使用成員函數指針來指向類中的非靜態成員函數,實例如下:

#include <iostream>
using namespace std;

class CAA{
public:
    static void ShowStatic(){
        cout << "CAA::ShowStatic()" << endl;
    }
    void Show(){
        cout << "CAA::Show()" << endl;
    }
};

typedef void (CAA::*PFUN_CAA)();    //定義成員函數指針

int main()
{
    PFUN_CAA pfun_3 = &CAA::Show;
    //(*pfun_3)();      // 不能直接調用,因爲這個函數指針指向的是普通成員函數,調用還是需要對象的!
    CAA aa;
    (aa.*pfun_3)();
    CAA* a = new CAA;
    (a->*pfun_3)();


    system("pause");
    return 0;
}

如上例所示,成員函數指針定義的時候就指向了是屬於哪個類的,如果我們將CAA類的成員函數指針指向了CBB類的成員函數,那麼就要對其函數地址進行強轉才能編譯通過,並且如果CBB類的成員函數中用到了成員屬性,那麼這些屬性的類型和定義的順序都要和CAA中定義的相同!否則就會出錯!因爲:我們強轉爲CAA類的函數地址之後,再使用這個函數指針的時候還是要通過CAA的對象使用,那麼就會用到CAA對象地址中的成員屬性。所以上述兩點一定要相同!實例如下:

#include <iostream>
using namespace std;

class CAA{
public:
    CAA(int nValue, double dValue){
        this->m_nValue = nValue;
        this->m_dValue = dValue;
    }
    void Show(){
        cout << "CAA::Show()," << this->m_nValue << endl;
    }
private:
    double m_dValue;
    int m_nValue;
};

class CBB{
public:
    void Show(){
        cout << "CBB::Show(), " << this->m_nValue << ", " << this->m_dValue << endl;
    }
private:
    double m_dValue;
    int m_nValue;   //這裏如果不是 int 型的就會發生問題,因爲 CAA 的對象在調用 CBB 函數的時候將 CAA 的 this 指針傳進來了,所以 Show 函數中所有使用的成員屬性都必須和 CAA 中的成員相符,否則就會亂了。
    //注意:這裏的類型相符具體到:成員屬性定義的順序都要一樣!否則,此例中如果CBB的這兩個成員屬性定義的順序顛倒了,那麼輸出的結果就錯誤了。
};

typedef void (CAA::*PFUN_CAA)();

int main()
{
    PFUN_CAA pFun = (PFUN_CAA)&CBB::Show;   //強轉爲 PFUN_CAA 類型
    CAA* pA = new CAA(65, 12.34);
    (pA->*pFun)();

    system("pause");
    return 0;
}

模擬虛函數列表

虛函數列表功能分析:我們知道虛函數列表其實就是一個指針數組,裏面存的都是虛函數的地址,普通繼承的情況下子類的vptr(指向虛函數列表的指針)都是由父類繼承來的,只有虛繼承並且父類存在虛函數的情況下,子類纔會有一個自己的vptr(這裏不明白可以看我的《C++知識點隨筆(五):虛繼承》一篇)。由於編譯的時候虛函數列表就創建了,所以這個指針數組要是 static 的。下面我們通過成員函數指針的知識來模擬虛函數列表的實現:

#include <iostream>
using namespace std;

class CFather;
typedef void (CFather::*PFUN)();    //定義一個父類的成員函數指針

class CFather{
public:
    CFather(){
        this->pFun = arr;    //讓vptr指向父類的虛函數列表
    }
    void AA(){
        cout << "CFather::AA()" << endl;
    }
    void BB(){
        cout << "CFather::BB()" << endl;
    }
    void CC(){
        cout << "CFather::CC()" << endl;
    }
public:
    static PFUN arr[];    //父類的虛函數列表
    PFUN* pFun;     //vptr
};
PFUN CFather::arr[] = {&CFather::AA, &CFather::BB, &CFather::CC};
//靜態成員類外初始化

class CSon : public CFather{
public:
    CSon(){
        CFather::pFun = this->arr;
    }
    void AA(){
        cout << "CSon::AA()" << endl;
    }
    void BB(){
        cout << "CSon::BB()" << endl;
    }
public:
    static PFUN arr[];    //子類的虛函數列表
};
PFUN CSon::arr[] = {(PFUN)&CSon::AA, (PFUN)&CSon::BB, &CFather::CC};
//子類重寫了父類的AA和BB虛函數,所以在子類的虛函數列表中對應位置的函數指針就指向了子類的函數的地址。

void ShowFatherVirtualFunction(CFather* pFather)    //打印父類的虛函數
{
    for (int i = 0; i < 3; ++i)
    {
        (pFather->*(*(pFather->pFun)++))();
    }
}

int main()
{
    CFather* pFather1 = new CFather;   //父類的對象
    ShowFatherVirtualFunction(pFather1);

    cout << "-----------------------------" << endl;

    CFather* pFather2 = new CSon;      //父類指針指向子類對象
    ShowFatherVirtualFunction(pFather2);

    system("pause");
    return 0;
}

結果:
這裏寫圖片描述

發佈了27 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章