本身開發了一個股票智能分析軟件,功能很強大,須要的點擊下面的連接獲取: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 多層繼承的子類
C++中的虛函數的做用主要是實現了多態的機制。關於多態,子類對象賦值給父類指針,而後經過父類的指針調用實際子類覆蓋父類的虛函數實現。賦值不一樣的子類對象給父類指針,可使用父類指針調用不一樣子類的虛函數(必須是覆蓋父類的函數),這種技術可讓父類的指針有「多種形態」。
每一個子類對象建立時,會有一個虛函數表,並且虛函數表在對象首地址開始,以達到高效訪問的目的。一個子類繼承多個父類時,子類對象的頭部有多個虛函數表,把父類的虛函數表複製過來,按照繼承順序排列。若是子類覆蓋了父類的虛函數,則替換掉複製過來的父類虛函數表中對應位置的虛函數。子類中的自定義虛函數,在第一個虛函數表中,排在父類虛函數後面(主流說法是這樣,可是實際測試,並無)。當用子類的對象賦值給父類指針時,父類指針指向了子類虛函數表,當調用函數時,就會從子類虛函數表中查找函數,去調用子類覆蓋父類的虛函數,這樣就實現了多態。
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;
};
建立一個對象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,而且去訪問這些私有函數。
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。
總結:單繼承的子類對象虛函數表中,只包含父類對象的虛函數和覆蓋的子類虛函數,子類虛函數會替換掉對應位置的父類虛函數。子類自定義的虛函數不在虛函數表中。
(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.
總結:子類繼承多個父類,子類對象中包含了三個父類的虛函數表,按照繼承順序排列。子類覆蓋的虛函數會替換對應位置的三個父類虛函數。子類自定義的虛函數也不在虛函數表中。
子類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和覆蓋基類的虛函數。虛函數表會保留繼承層級關係。會有兩個虛函數表,表中的函數地址相同。