c++多態和虛函數表實現原理

 

本身開發了一個股票智能分析軟件,功能很強大,須要的點擊下面的連接獲取:html

http://www.javashuo.com/article/p-kahdodke-ge.html函數

C++多態以及虛函數表實現原理測試

 

目錄spa

1      定義指針

2      虛函數表實現原理htm

3      實例解析對象

3.1     定義父類blog

3.2     父類對象地址空間剖析繼承

3.3     子類繼承父類的虛函數表內存

3.3.1     單繼承覆蓋和不覆蓋對比

3.3.2     子類繼承多個父類

3.3.3     多層繼承的子類

 

1        定義

C++中的虛函數的做用主要是實現了多態的機制。關於多態,子類對象賦值給父類指針,而後經過父類的指針調用實際子類覆蓋父類的虛函數實現。賦值不一樣的子類對象給父類指針,可使用父類指針調用不一樣子類的虛函數(必須是覆蓋父類的函數),這種技術可讓父類的指針有「多種形態」。

2        虛函數表實現原理

每一個子類對象建立時,會有一個虛函數表,並且虛函數表在對象首地址開始,以達到高效訪問的目的。一個子類繼承多個父類時,子類對象的頭部有多個虛函數表,把父類的虛函數表複製過來,按照繼承順序排列。若是子類覆蓋了父類的虛函數,則替換掉複製過來的父類虛函數表中對應位置的虛函數。子類中的自定義虛函數,在第一個虛函數表中,排在父類虛函數後面(主流說法是這樣,可是實際測試,並無)。當用子類的對象賦值給父類指針時,父類指針指向了子類虛函數表,當調用函數時,就會從子類虛函數表中查找函數,去調用子類覆蓋父類的虛函數,這樣就實現了多態。

3        實例解析

3.1   定義父類

class Base {

            public:

            virtual void f() { cout << "Base::f" << endl; }

 

            virtual void g() { cout << "Base::g" << endl; }

 

            virtual void h() { cout << "Base::h" << endl; }

int a=0;

};

3.2   父類對象地址空間剖析

建立一個對象Base b;

父類的虛函數表示意圖爲

 

 

 

 

 虛函數表在對象地址首部,函數指針按照定義的順序在虛函數表中,能夠經過地址訪問的方式去訪問這些函數。

int main()

{

       typedef void(*Fun)(void);

 

       Base b;

       cout << "虛函數表地址:" << (int*)(&b) << endl;

       cout << "1" << *(int*)(&b) << endl;

       cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl;

       Fun funf = ((Fun)*((int*)*(int*)(&b) + 0));

       Fun fung= ((Fun)*((int*)*(int*)(&b) + 1));

       Fun funh= ((Fun)*((int*)*(int*)(&b) + 2));

       funf();

       fung();

       funh();

       system("pause");

 

    return 0;

}

輸出結果

 

 

 

 

b地址空間以下圖所示:

 

 

 

 

 

理解下面一句是怎麼獲取到函數地址的。

pFun = (Fun)*((int*)*(int*)(&b));

(&b)獲取到對象b的地址。

(int*)(&b))轉化爲int*的指針,對象b的前4個字節是虛函數表的地址,int* 也是四個字節,這種強制轉換,是把&a開始的4個字節看成一個總體,也就是虛函數表的地址,虛函數表地址在對象地址空間首部。

 *(int*)(&b))  *虛函數表地址,獲得的是虛函數表的內存空間,虛函數表內存空間首部是虛函數f的地址,即Base::f()的地址。

 ((int*)*(int*)(&b))在轉換爲int*類型的指針,就獲得了四個字節的虛函數f的地址。f,g,h的地址各佔四個字節,int*的指針+1,就是向後移動四個字節,獲得g函數的地址,+2,就是再向後移動四個字節,獲得h函數的地址。

*((int*)*(int*)(&b))  *虛函數地址,獲得的是虛函數f的實際的存儲空間。

(Fun)*((int*)*(int*)(&b));轉換爲函數指針。

 

思考:若是虛函數fgh是private私有的,那麼經過這種方式仍然能夠獲取到私有函數的地址fgh,而且去訪問這些私有函數。

3.3   子類繼承父類的虛函數表

3.3.1         單繼承覆蓋和不覆蓋對比

class Derver: public Base{

public:

       virtual void f1() { cout << "Derver::f" << endl; }

 

       virtual void g1() { cout << "Derver::g" << endl; }

 

       virtual void h1() { cout << "Derver::h" << endl; }

};

查看無覆蓋和有覆蓋繼承的狀況,右邊子類覆蓋了父類的f函數。

 

無覆蓋的子類對象地址空間。網上說父類虛函數在虛表前面,子類虛函數在後面

 

 

 

可是實際狀況是虛函數表中只有父類虛函數,以下所示

 

 

 

有覆蓋的子類對象地址空間,子類虛函數表的首位替換了子類覆蓋的函數

 

 

 

虛函數表中是子類虛函數替換了父類虛函數的位置,也沒有未覆蓋的子類虛函數f1和g1。

 

 

 

 

總結:單繼承的子類對象虛函數表中,只包含父類對象的虛函數和覆蓋的子類虛函數,子類虛函數會替換掉對應位置的父類虛函數。子類自定義的虛函數不在虛函數表中。

3.3.2         子類繼承多個父類

(1)   定義一個子類繼承三個基類,繼承關係以下圖

 

 

 

代碼實現

class Base1 {

public:

       virtual void f() { cout << "Base1::f" << endl; }

 

       virtual void g() { cout << "Base1::g" << endl; }

 

       virtual void h() { cout << "Base1::h" << endl; }

};

class Base2 {

public:

       virtual void f() { cout << "Base2::f" << endl; }

 

       virtual void g() { cout << "Base2::g" << endl; }

 

       virtual void h() { cout << "Base2::h" << endl; }

};

class Base3 {

public:

       virtual void f() { cout << "Base3::f" << endl; }

 

       virtual void g() { cout << "Base3::g" << endl; }

 

       virtual void h() { cout << "Base3::h" << endl; }

};

class Mult :public Base1, public Base2,public Base3 {

public:

       virtual void f() { cout << "Mult::f" << endl; }

 

       virtual void g1() { cout << "Mult::g" << endl; }

 

};

class Grander :public Derver

{

public:

       virtual void f1() { cout << "Grander::f" << endl; }

 

       virtual void g1() { cout << "Grander::g1" << endl; }

};

(2)   查看子類對象的地址空間

以下圖所示,子類對象空間有三個基類虛表,子類覆蓋的函數f替換了虛表中對應的位置。子類自有的虛函數網上說是在第一個虛表後面。可是實際卻沒有。

 

 

 

以下圖所示,是實際的地址空間,第一個虛表中並無看到子類的虛函數g1.

 

 

 

總結:子類繼承多個父類,子類對象中包含了三個父類的虛函數表,按照繼承順序排列。子類覆蓋的虛函數會替換對應位置的三個父類虛函數。子類自定義的虛函數也不在虛函數表中。

 

3.3.3         多層繼承的子類

子類Derver繼承父類Base,而後Grander類繼承Derver類。

代碼實現以下

class Base {

private:

    virtual void f() { cout << "Base::f" << endl; }

 

    virtual void g() { cout << "Base::g" << endl; }

 

    virtual void h() { cout << "Base::h" << endl; }

    int a = 0;

};

class Derver: public Base{

public:

    virtual void f() { cout << "Derver::f" << endl; }

    virtual void g1() { cout << "Derver::g1" << endl; }

    virtual void h1() { cout << "Derver::h1" << endl; }

    int d =1;

};

class Grander :public Derver

{

public:

    virtual void f() { cout << "Grander::f" << endl; }

    virtual void g1() { cout << "Grander::g1" << endl; }

};

內存虛表結構以下所示,只虛表中只有有Base類中函數Base::g(),Base::h(),還有覆蓋的子類函數Grander::f()。其餘的函數都沒有。連覆蓋Derver的Grander::g1()都沒有。這是什麼鬼?

 

 

 

總結:多層繼承,子類對象的虛函數表中值包含Base類的虛函數,子類覆蓋的虛函數替換掉對應位置的Base類虛函數。中間的Derver虛函數和Grander中自定義的虛函數不存在虛函數表中。

(1)基類Base指針指向對象Derver

Base* pB = new Derver();

獲得的內存結構以下圖所示

有兩個虛擬表,虛函數表中地址相同,函數是基類的虛函數和Derver覆蓋基類的虛函數。

 

 

 

(2)中間的指針Derver*指向孫類對象Grander

獲得的虛表結構以下,虛函數是基類base虛函數和Grander覆蓋基類的虛函數Grander::f();

Derver* pD  = new Grander();

 

 

 

(3)基類Base指針指向孫類對象Grander

獲得的虛函數表以下圖所示

Base* pDG = new Grander();

 

 

 

總結:多層繼承時,虛函數表中保存的是基類的虛函數Base和覆蓋基類的虛函數。虛函數表會保留繼承層級關係。會有兩個虛函數表,表中的函數地址相同。

相關文章
相關標籤/搜索