1. 程式人生 > >【C++】——多型(下)再探虛表&不同繼承下帶有虛擬函式的物件模型

【C++】——多型(下)再探虛表&不同繼承下帶有虛擬函式的物件模型

一、虛擬函式

1、概念:簡單地說,那些被virtual關鍵字修飾的成員函式,就是虛擬函式。虛擬函式的作用,用專業術語來解釋就是實現多型。
2、程式碼示例:

class Base
{
public:
    virtual void TestFunc1()
    {
        cout << "Base::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "Base::TestFunc2()" << endl;
    }
    virtual
void TestFunc3() { cout << "Base::TestFunc3()" << endl; } int _b; }; typedef void(*PVTF)(); void PrintVTF(Base& b)//列印虛擬函式 { PVTF* pVTF = (PVTF*)(*(int*)&b); while (*pVTF)//走到最後一個位置,解引用就是一個0 { (*pVTF)();//調這個函式 ++pVTF; } cout << endl; } int
main() { cout << sizeof(Base) << endl;//8 Base b; b._b = 1; PrintVTF(b); return 0; }

上面列印虛擬函式的函式裡面迴圈條件——走到最後一個位置,解引用就是一個0

分析:一個類中的虛擬函式
(1)如果一個類裡面有虛擬函式,其類大小會多4個位元組
其大小為:成員大小總和+4位元組
注意:4位元組是多了一個指向虛表的指標(虛擬函式的地址即虛擬函式的表格,簡稱虛表)
(2)以下結果可以說明:虛擬函式表格裡面的虛擬函式排列次序就是它們在類裡面宣告的先後順序


此結果中類的大小為8——成員大小4+4(有虛擬函式)=8

3、定義虛擬函式的限制:

(1)非類的成員函式不能定義為虛擬函式。

(2)類的成員函式中靜態成員函式和建構函式也不能定義為虛擬函式,但可以將解構函式定義為虛擬函式。

(3)常常把基類的解構函式定義為虛擬函式。因為,將基類的解構函式定義為虛擬函式後,當利用delete刪除一個指向派生類定義的物件指標時,系統會呼叫相應的類的解構函式。而不將解構函式定義為虛擬函式時,只調用基類的解構函式。

(4)只需要在宣告函式的類體中使用關鍵字“virtual”將函式宣告為虛擬函式,而定義函式時不需要使用關鍵字“virtual”。

(5)當將基類中的某一成員函式宣告為虛擬函式後,派生類中的同名函式(函式名相同、引數列表完全一致、返回值型別相關)自動成為虛擬函式。

(6)如果聲明瞭某個成員函式為虛擬函式,則在該類中不能出現和這個成員函式同名並且返回值、引數個數、型別都相同的非虛擬函式。在以該類為基類的派生類中,也不能出現和這個成員函式同名並且返回值、引數個數、型別都相同的非虛擬函式。

二、不同繼承下帶有虛擬函式的物件模型

1、帶有虛擬函式的單繼承

class Base
{
public:
    virtual void TestFunc1()
    {
        cout << "Base::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "Base::TestFunc2()" << endl;
    }
    virtual void TestFunc3()
    {
        cout << "Base::TestFunc3()" << endl;
    }

    int _b;
};

class Derived :public Base
{
public:
    int _d;
};

typedef void(*PVTF)();
void PrintVTF(Base& b,const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout <<str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
int main()
{
    cout << sizeof(Base) << endl;//8
    cout << sizeof(Derived) << endl;//12

    Base b;
    b._b = 1;

    Derived d;
    d._b = 1;
    d._d = 2;

    PrintVTF(b,"Base VTF:");
    PrintVTF(d, "Derived VTF:");
    return 0;
}

結果:
此時派生類中沒有虛擬函式的列印結果

派生類的物件模型

——在派生類中加上一個虛擬函式(重寫了之後,換成派生類自己的虛擬函式)

class Derived :public Base
{
public:
    virtual void TestFunc2() //重寫Base類中的TestFunc2()
    {
        cout << "Derived::TestFunc2()" << endl;
    }

    int _d;
};

經過派生類的重寫,打印出來以後是派生類自己的TestFunc2()

總結:

(1)基類中的虛表:將類中的虛擬函式按照在類中宣告的先後次序新增虛擬函式表中
(2)派生類中的虛表:

——將基類虛擬函式表中的內容拷貝一份(從第一個列印即如果中可以看出)。
——如果派生類重寫了基類的某個虛擬函式,使用派生類自己的虛擬函式替換派生類虛表中相同偏移量位置的基類的虛擬函式(從第二個列印即如果中可以看出)。按照派生類自己特有的虛擬函式的宣告次序將其增加到虛擬函式表的最後。

2、帶有虛擬函式的多繼承

class B1
{
public:
    virtual void TestFunc1()
    {
        cout << "B1::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "B1::TestFunc2()" << endl;
    }
public:
    int _b1;
};

class B2
{
public:
    virtual void TestFunc3()
    {
        cout << "B2::TestFunc3()" << endl;
    }
    virtual void TestFunc4()
    {
        cout << "B2::TestFunc4()" << endl;
    }
public:
    int _b2;
};

class D : public B1, public B2
{
public:
    virtual void TestFunc2()
    {
        cout << "D::TestFunc2()" << endl;
    }
    virtual void TestFunc3()
    {
        cout << "D::TestFunc3()" << endl;
    }
public:
    int _d;
};

#include<string>
typedef void(*PVTF)();
void PrintVTF(B1& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
void PrintVTF(B2& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
int main()
{
    cout << sizeof(D) << endl;

    D d;
    d._b1 = 1;
    d._b2 = 2;
    d._d = 3;

    B1& b1 = d;
    PrintVTF(b1, "D VFT— > B1");
    B2& b2 = d;
    PrintVTF(b2, "D VFT— > B2");
    return 0;
}

派生類物件模型
將派生類新增加的虛擬函式放置到第一張虛表的最後。

3、帶有虛擬函式的菱形繼承(存在二義性)

class B
{
public:
    virtual void TestFunc1()
    {
        cout << "B::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "B::TestFunc2()" << endl;
    }
public:
    int _b;
};

class C1 :public B
{
public:
    virtual void TestFunc1()//重寫B裡面的TestFunc1()
    {
        cout << "C1::TestFunc1()" << endl;
    }
    virtual void TestFunc3()//自己新增一個TestFunc3()
    {
        cout << "C1::TestFunc3()" << endl;
    }

    int _c1;
};

class C2 :public B
{
public:
    virtual void TestFunc2()//重寫B裡面的TestFunc2()
    {
        cout << "C2::TestFunc2()" << endl;
    }
    virtual void TestFunc4()//自己新增一個TestFunc4()
    {
        cout << "C2::TestFunc4()" << endl;
    }

    int _c2;
};

class D :public C1, public C2
{
public:
    virtual void TestFunc1()//重寫
    {
        cout << "D::TestFunc1()" << endl;
    }
    virtual void TestFunc3()//重寫
    {
        cout << "D::TestFunc3()" << endl;
    }
    virtual void TestFunc4()//重寫
    {
        cout << "D::TestFunc4()" << endl;
    }
    virtual void TestFunc5()//自己新增的
    {
        cout << "D::TestFunc5()" << endl;
    }

    int _d;
};

#include<string>
typedef void(*PVTF)();

void PrintVTF(C1& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
void PrintVTF(C2& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
int main()
{
    cout << sizeof(D) << endl;//28
    D d;
    d.C1::_b = 1;
    d._c1 = 2;

    d.C2::_b = 3;
    d._c2 = 4;
    d._d = 5;

    C1& c1 = d;
    PrintVTF(c1, "D VFT—>C1:");
    C2& c2 = d;
    PrintVTF(c2, "D VFT—>C2:");
    return 0;
}

派生類物件模型

4、帶有虛擬函式的虛擬繼承

class B
{
public:
    virtual void TestFunc1()
    {
        cout << "B::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "B::TestFunc2()" << endl;
    }
public:
    int _b;
};

class D :virtual public B
{
public:
    virtual void TestFunc2()//重寫
    {
        cout << "D::TestFunc2()" << endl;
    }
    virtual void TestFunc3()//自己新增的
    {
        cout << "D::TestFunc3()" << endl;
    }

    int _d;
};

#include<string>
typedef void(*PVTF)();

void PrintVTF(B& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}

int main()
{
    cout << sizeof(D) << endl;//16

    int a = -4;
    D d;
    d._b = 1;
    d._d = 2;

    B& b = d;
    PrintVTF(b, "D—>B:");
    PrintVTF(d, "D—>D:");
    return 0;
}

派生類物件的模型

(1)派生類未增加新的虛擬函式

(2)派生類增加了新的虛擬函式

5、帶有虛擬函式的菱形虛擬繼承

class B
{
public:
    virtual void TestFunc1()
    {
        cout << "B::TestFunc1()" << endl;
    }
    virtual void TestFunc2()
    {
        cout << "B::TestFunc2()" << endl;
    }
    int _b;
};

class C1 :virtual public B
{
public:
    virtual void TestFunc1()//重寫B裡面的TestFunc1()
    {
        cout << "C1::TestFunc1()" << endl;
    }
    virtual void TestFunc3()//自己新增一個TestFunc3()
    {
        cout << "C1::TestFunc3()" << endl;
    }

    int _c1;
};
class C2 :virtual public B
{
public:
    virtual void TestFunc2()//重寫B裡面的TestFunc2()
    {
        cout << "C2::TestFunc2()" << endl;
    }
    virtual void TestFunc4()//自己新增一個TestFunc4()
    {
        cout << "C2::TestFunc4()" << endl;
    }

    int _c2;
};

class D :public C1, public C2
{
public:
    virtual void TestFunc1()//重寫
    {
        cout << "D::TestFunc1()" << endl;
    }
    virtual void TestFunc3()//重寫
    {
        cout << "D::TestFunc3()" << endl;
    }
    virtual void TestFunc4()//重寫
    {
        cout << "D::TestFunc4()" << endl;
    }
    virtual void TestFunc5()//新增加的
    {
        cout << "D::TestFunc5()" << endl;
    }

    int _d;
};

#include<string>
typedef void(*PVTF)();
void PrintVTF(B& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
void PrintVTF(C1& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}
void PrintVTF(C2& b, const string& str)//列印虛擬函式
{
    PVTF* pVTF = (PVTF*)(*(int*)&b);
    cout << str << endl;
    while (*pVTF)//走到最後一個位置,解引用就是一個0
    {
        (*pVTF)();//調這個函式
        ++pVTF;
    }
    cout << endl;
}

int main()
{
    cout << sizeof(D) << endl;//36
    D d;
    d._b = 1;
    d._c1 = 2;
    d._c2 = 3;
    d._d = 4;

    B& b = d;
    PrintVTF(b, "D VFT—>B:");
    C1& c1 = d;
    PrintVTF(c1, "D VFT—>C1:");
    C2& c2 = d;
    PrintVTF(c2, "D VFT—>C2:");
    return 0;
}

派生類的物件模型
(1)移量表格

(2)虛表

經過程式碼驗證,結果正確

附加:在C1、C2和D類里加入建構函式或者解構函式,則派生類大小多了4位元組