多重繼承實在是一個折磨人的東西。。。
18.3 多重繼承和虛繼承
18.3.1 多重繼承
派生類的派生類表可以繼承多個類,默認的繼承方式和單繼承是一樣的,即class類默認使用private,struct默認使用public。
派生類構造函數初始化所有基類的時候,我們一般會在初始化列表中調用基類的構造函數。一個多重繼承的派生類其構造順序是按照派生列表中聲明的順序進行的。
對於多重繼承,一個相同的基類只在繼承體系中出現一次。
繼承的構造函數和多重繼承
之前瞭解到基類是可以繼承直接基類的構造函數的,在多重繼承中同樣是可以的,但是如果兩個直接基類的構造函數的形參是一樣的,那麼會導致編譯發生錯誤。解決的辦法就是自己定義一個相同形參列表的構造函數。
書上的這個例子就很形象。
多重繼承的派生類的拷貝和移動操作
對一個多重繼承的派生類進行拷貝、賦值的操作時,編譯器合成的拷貝構造函數和賦值構造函數會先調用基類對應的函數執行相關操作。
比如書中的例子:
Panda ying_ying("ying_ying");
Panda beibei = ying_ying;
類的繼承體系爲
ZoonAnimal
Bear Endangered
Panda
beibei將調用拷貝構造,首先執行ZooAnimal的拷貝構造,然後執行Bear的拷貝構造,再執行Endangered的拷貝構造,最後再執行自己拷貝構造函數。
練習
18.21
a.CADVehicle使用private的方式繼承Vehicle。
b.同一個基類繼承了兩次
c。無
18.22
構造mi首先會調用C類的構造函數,C類繼承自B,所以在執行C類的構造函數體之前將執行B類的構造函數,而B繼承自A,所以將執行A的構造函數。在這一條派生鏈中,構造的順序爲
A B C
然後調用Z的構造函數,Z繼承自X和Y,所以先調用X,再調用Y,調用順爲
X Y Z
所以整個的調用順序爲
A B C X Y Z MI
18.3.2 類型轉換和多個基類
多重繼承的下的基類的指針和引用可以綁定子類的對象,這和單繼承是一樣的。同樣他們只能訪問靜態類型所限定的成員。
練習
18.23
根據前面學到的,A,B,C,X都是D的直接或者間接基類,所以類型轉換都是允許的,所以這裏每一個類型轉換都是允許的。
class A {
};
class B:public A {
};
class C :public B {
};
class X {
};
class Y {
};
class D :public X, public C {
};
D *pd = new D();
C* pc = pd;
X* px = pd;
A* pa = pd;
B* pb = pd;
18.24
僅print和析構函數可以調用
18.25
a,b,c都是調用MI的print
d,e,f都是先調用MI,再調用D2,Base2,D1,Base1的析構函數
18.3.3 多重繼承下的類作用域
在單繼承中,派生類的作用域在基類和間接基類的作用域中,所以進行名字查找的時候,如果派生類中沒有則取基類中。
在多重繼承中也是一樣的,但是如果兩根不同的繼承鏈下有相同名字的成員,則會產生二義性。如果我們在使用派生類的時候,不訪問這個成員,那麼程序是不會報錯的。
就算這個成員在某一個繼承鏈下是私有的,在另外一個繼承鏈下是公有的,訪問這個成員同樣會出錯。
如果兩個繼承鏈下基類函數名相同,但是形參列表不一樣,還是會導致二義性。
練習
18.26
首先我以爲如果形參列表不同可以重載,通過自己編寫代碼發現了這樣是不行的。其實書上寫了如果形參列表不同也會引發二義性,只是自己沒有看到
mi.Base1::print(42)
18.27
MI自己的成員有
ival,dvec
Base2中繼承下來的成員有
fval
在Derived中繼承下來的成員有
ival,dval,cval,sval
因爲MI自己寫了的成員隱藏基類的同名成員,
所以從基類中繼承了
fval,cval,sval,dval
a,ival,dvec,fval,cval,sval,dval
b,沒有
c,
dval = Base1::dval+Derived::dval;
d,
if(dvec.size()>0){
Base2::fval=dvec[dvec.size()-1];
}
e,
Derived::sval[0]=Base1::cval;
18.3.4 虛繼承
之前說了一個基類最多在繼承體系中出現一次,但是我們有的時候還是不可避免的隱式繼承了多次,比如
A是B和C的直接基類,D繼承了B和C,那麼D其實隱式的繼承了兩次A。
這樣生成一個D對象的時候,它會生成兩個A的子對象,爲了避免這樣的情況發生,C++提供了虛繼承,所謂的虛繼承就是把基類共享了。
我們可以在派生列表中使用虛繼承
struct B:public virtual A;
struct C:public virtual A;
虛基類不會影響派生類的使用
練習
18.28
無需加前綴
ival
bar
必須假前綴
foo
cval
18.3.5 構造函數和虛繼承
被虛繼承的類,叫做虛基類**,虛基類是通過最底層的派生類來初始化的。**,因爲這樣可以避免虛基類被初始化多次。
包含了虛基類的派生類的構造函數執行順序和普通多繼承的構造函數執行順序不一樣,包含虛基類的派生類,首先執行虛基類的構造函數,然後再按照派生列表的順序來執行構造函數。
同樣對於析構,則是按照構造函數相反的順序來析構。
如果一個派生類包含多個虛基類,則其將在派生類表中從左往右檢查每一個繼承的類,是否包含虛基類,如果包含則調用虛基類的函數,調用完所有的虛基類函數之後,再從左到右開始調用普通類的析構函數。
書中的這一部分,講解得非常好。
練習
18.29
a.
Class,Base,D1,D2,MI,Class,Final
b.
一個Base,兩個class
c.
(a)錯誤,派生類的指針不能直接指向基類的對象
(b)正確
(c) 錯誤
(d) 正確,基類的指針可以保存派生的地址,這種形式也是可以的。
18.30
略