继承和多态 3.0 -- 菱形继承
單繼承和多繼承
C++的繼承方式是支持單繼承和多繼承的,首先看一下代碼,分清單繼承和多繼承
單繼承
class A
{
public:int _a;
};class B :public A
{
public:int _b;
};class C : public A
{
public:int _c;
};
類似于上面的方式就是單繼承,或者C類可以不繼承A類,而是直接繼承B類,這種方式也是單繼承
多繼承
class D :public B,public C
{
public:int _d;
};
上面的這種方式就是多繼承,即是D類既繼承了B類,又繼承了C
通過上一個章節的學習我們知道,繼承的時候,在子類的 成員變量里面包含了父類的成員變量,則我們可以分析出,上面的繼承 關系中,B類里面包含變量_a,和變量_b;C類里面包含了變量_a和變量_c;這個時候我們的D類里面就包含了B類的成員變量和C類的成員變量,所以D類里面包含的成員變量是_a,_b,_a,_c,_d。
菱形繼承
基于上面的分析我們引入了菱形繼承,下面我們用一個圖來表示上面的成員的關系
菱形繼承問題 – 二義性和數據冗余
還是上面的代碼,這個時候我們實例化一個D類的對象,這個時候我們想對_a賦值,就會出現報錯,請看下面的代碼
D d;d._a = 1;return 0;
這個時候編譯器報的錯誤就是D::_a不明確,為什么不明確呢,這個從上面的圖中可以看出來,因為我們的D類的對象中有有兩個_a的成員變量,這個時候對_a賦值的時候,編譯器不知道該給哪個_a賦值,這個問題就是====二義性====問題。
首先看下面的解決辦法
D d;d.B::_a = 1;
通過這種方式我們就不會出現二義性的問題了。
還有一個問題就是,我們的D類型的對象中,有兩個_a這個時候的_a表示的意義都是一樣的,如果我們的A類里面的成員變量非常的大,這個時候是不是就是==數據冗余==呢,我們需要一個A類的成員就夠了。
解決菱形繼承 – 虛擬繼承
為了解決上面的問題,我們引入了虛擬繼承的概念,請看下面的代碼
class A
{
public:int _a;
};class B :virtual public A
{
public:int _b;
};class C : virtual public A
{
public:int _c;
};class D :public B,public C
{
public:int _d;
};int main()
{D d;d._a = 1;return 0;
}
這個時候在主函數中,再次使用上面的方式對_a賦值的時候,就不會報錯了,那么我們不禁想問,編譯器到底是如何做的呢,為什么這么做的時候就不會出現問題了呢,這個問題要從C++對象模型說起
虛繼承對象模型
下面是普通的繼承,沒有虛擬繼承,我們結合旁邊的代碼可以看到,如果沒有虛擬繼承的時候,D類的對象中會為兩個A類型里面的_a變量,這就是數據冗余和二義性
下面我們再來看一下如果我們使用的是虛擬繼承給賦值的給賦值的時候是什么結果呢,看下面的截圖
這個時候我們注意到一個問題是,和剛剛的那個截圖不一樣的是,在變量_b的上面放置的不是一個變量_a,而是一個類似于一個地址一樣的數據,同樣的道理,在變量_c的上面放置的也不是變量_a,也是一個地址一樣的內容,我們又來發現一個問題就是,我們給變量_a,賦值的地方顯示的是在變量_d的下面,然后我們再來看看那個類似于地址一樣的內容到底是什么呢
我們把這個類似地址的數據放置放在監視窗口中看一下
這個時候我們看到,監視窗口2里面放置的內存的位置放置的十六進制數是14,轉化成十進制就是20,我們再來比較一下監視窗口1中,那個地址和_a實際存放的位置的距離是20,這個時候我們就能夠理解了,原來這里放置的是_a這個變量的偏移量。
還有一個疑問就是,為什么監視窗口2中對應的地址上面放置的是0,而不是14呢,這是因為,這個0所在位置是為了給接下來的虛函數準備的,這個以后會討論到。
這里需要說明的是,監視窗口1中放置的是地址是虛基表指針,它指向的是一個虛基表。
我們還需要考慮的一個問題就是,為什么這里不直接放置_a的地址,而是放置的是相對位置的偏移量呢,這里我們分析這樣的一個問題,如果我們拿這個類去實例化多個對象的話,是不是每個對象就有一個存放_a的地址呢,那么這個時候,每個對象的放置的地址都一樣了,而如果放置的是偏移量的話,每個對象的地址空間中放置的內容都是一樣的,這就減少了計算的開銷。
總結
以上是生活随笔為你收集整理的继承和多态 3.0 -- 菱形继承的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求钢铁侠1,2,3部超清或更高的版本的迅
- 下一篇: 16款东风日产轩逸原装轮胎价格?