一:首先來總結一下兩者:
虛繼承:可以解決多重繼承的問題,使得基類可以被多次繼承。
如:
class A{//成員}class B:public A {//成員}class C:public A {//成員a}class D:public B,public C{//成員a}
這樣在建立D的時候,會建立2個A類,增加了開銷。並且如果有
D d;
d.a=0;//會有錯誤產生,因為不能確定是從B還是C類中繼承來的成員a
//必須改成下面的文法才正確
D d;
d.B::a = 0;
d.C::a = 0;
若改為:
class A{//成員}class B:public virtual A {//成員}class C:public virtual A {//成員函數test()}class D:public B,public C{//成員函數test()}
這樣在建立D的時候,只用建立1個共用的A類,相較於上一段代碼,節省了開銷
虛函數繼承:是物件導向多台的主要方式,在成員函數的聲明中加入virtual來實現,說白了就是可以在衍生類別中對基類的續成員函數重新編寫實現不同的操作。而且虛函數的特性會被繼承。
例:
class A{public:virtual test(){//實現1}}class B:public A {virtual test(){//實現2}}
這樣B類中的test()覆蓋了A類中的test(),且B類中的test()成員函數不必加上virtual限定也會預設其類型是virtual
二:現在我要描述我遇到的坑爹問題了:關於虛函數表的問題
當一個類有1個或多個虛函數時,編譯器為了實現虛函數,會為這個類建立一個虛函數表vtbl。這個表記錄了一個類中每一個虛函數成員的記憶體位址(按順序排列)。例如,基類中含有一個指標,指向該虛函數表;衍生類別對象裡則包含有一個獨立的指標,這個指標指向上面說的虛函數表。
對於每個類,編譯器都要建立一個虛函數地址表(以數組的形式實現就可以了)!
現在,有了第一個問題:衍生類別中重新定義了從基類繼承來的虛函數,這個虛函數表應該怎麼改進!!
如果一個虛函數被繼承下來,而且在衍生類別中沒有改變,那麼這個虛函數表中的記憶體位址不會有改變,這個衍生類別的虛指標仍然指向這個虛函數在基類中的定義;
但是如果在衍生類別中改變了,虛函數表儲存衍生類別中重新定義後該虛函數的地址,是的指標會指向衍生類別中對這個虛函數的新定義上。
若衍生類別中新增加了虛函數,則將這個虛函數的地址也加入到虛函數表中。
但是值得注意的是:無論,在類中包含了多少個虛函數,都只需要在對象添加一個地址成員就可以了,改變的只是表的大小!
聽著像繞口令。。。。。。我用《程式員面試寶典》上的一個例子來讓自己理解記憶下!!
1都不是虛繼承:此時,衍生類別和基類使用的是同一個虛函數表,當衍生類別對基類中的虛成員函數重新實現的時候,直接將新的函數實現覆蓋基類的函數實現。
#include<iostream>#include<memory.h>#include<assert.h>using namespace std;class A{char k[3];public:virtual void aa(){};};class B : public A//非虛繼承{char j[3]; //加入一個變數是為了看清楚class中的vfptr放在什麼位置public:virtual void bb(){};};class C : public B//非虛繼承{char i[3];public:virtual void cc(){};};int main(int argc, char *argv[]){cout<<"sizeof(A)"<<sizeof(A)<<endl; cout<<"sizeof(B)"<<sizeof(B)<<endl; cout<<"sizeof(C)"<<sizeof(C)<<endl; return 0;}
運行結果為:sizeof(A)8; sizeof(B)12;sizeof(C)16
也就是說A中記憶體為4(A類的虛函數表指標)+3(char數組長度)=8(編譯器補齊)
B中記憶體為4(B類虛函數表指標)+3(A類中數組長度)+3(B類中數組長度)=12(編譯器補齊)
C中記憶體為4(C類虛函數表指標)+3(A類中數組長度)+3(B類中數組長度)+3(C類中數組長度)=16(編譯器補齊)
2當B虛繼承A類,C不虛繼承B類時
#include<iostream>#include<memory.h>#include<assert.h>using namespace std;class A{char k[3];public:virtual void aa(){};};class B : public virtual A//虛繼承{char j[3]; //加入一個變數是為了看清楚class中的vfptr放在什麼位置public:virtual void bb(){};};class C : public B//非虛繼承{char i[3];public:virtual void cc(){};};int main(int argc, char *argv[]){cout<<"sizeof(A)"<<sizeof(A)<<endl; cout<<"sizeof(B)"<<sizeof(B)<<endl; cout<<"sizeof(C)"<<sizeof(C)<<endl; return 0;}
運行結果為:sizeof(A)8; sizeof(B)20;sizeof(C)24
也就是說A中記憶體為4(A類的虛函數表指標)+3(char數組長度)=8(編譯器補齊)
B中記憶體為4(B類虛函數表指標)+4(B虛繼承時指向基類A的指標)+3(B類中數組長度)+8(A類記憶體大小)=20(編譯器補齊)
C中記憶體為20(B類的記憶體大小,C不是虛繼承,可以與B類功用一個虛函數表)+4(C類中數組長度)=24(編譯器補齊)
3當B、C均為虛繼承時
#include<iostream>#include<memory.h>#include<assert.h>using namespace std;class A{char k[3];public:virtual void aa(){};};class B : public virtual A{char j[3]; //加入一個變數是為了看清楚class中的vfptr放在什麼位置public:virtual void bb(){};};class C : public virtual B{char i[3];public:virtual void cc(){};};int main(int argc, char *argv[]){cout<<"sizeof(A)"<<sizeof(A)<<endl; cout<<"sizeof(B)"<<sizeof(B)<<endl; cout<<"sizeof(C)"<<sizeof(C)<<endl; return 0;}
運行結果為:sizeof(A)8; sizeof(B)20;sizeof(C)32
也就是說A中記憶體為4(A類的虛函數表指標)+3(char數組長度)=8(編譯器補齊)
B中記憶體為4(B類虛函數表指標)+4(B虛繼承時的虛類指標指向A類)+3(B類中數組長度)+8(A類的記憶體大小)=20(編譯器補齊)
C中記憶體為4(C類虛函數表指標)+4(C虛繼承時的虛類指標指向B類)+3(C類中數組長度)+20(A類的記憶體大小)=32(編譯器補齊)
菜鳥起步,歡迎拍磚。。。。。。