1.單繼承
1.概念
單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承。
圖示:
2.多繼承
2.1概念
多繼承:一個(gè)子類有兩個(gè)或以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承。
圖示:
2.2菱形繼承
1.概念
菱形繼承:菱形繼承是多繼承的一種特殊情況。即:一個(gè)類是另外幾個(gè)類的子類,而這幾個(gè)子類又是另外一個(gè)類的父類。
基本模型:
2.問(wèn)題
但是呢,菱形繼承卻有一些問(wèn)題:它會(huì)造成數(shù)據(jù)的冗余以及數(shù)據(jù)的二義性。比如下面,在Assistant的對(duì)象中Person成員會(huì)有兩份。
3.樣例理解
注:以下在VS2022 X64環(huán)境下驗(yàn)證。
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C : public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
二義性
如果我們直接訪問(wèn)d中的_a,編譯器不知道要訪問(wèn)繼承B的 _a還是繼承C的 _a,會(huì)有歧義。
數(shù)據(jù)冗余
首先觀察調(diào)試窗口:我們可以看到創(chuàng)建的d變量的地址。
然后通過(guò)內(nèi)存窗口,我們可以看到d中存儲(chǔ)了2個(gè)_a,相同的部分就會(huì)重復(fù)存儲(chǔ)。
對(duì)于內(nèi)存模型抽象化
2.3菱形虛擬繼承(解決菱形繼承的問(wèn)題)
1.概念
菱形虛擬繼承就是在菱形繼承的腰部繼承時(shí)(即父類第一次有多個(gè)子類時(shí))加上關(guān)鍵字virtual即可。
2.樣例理解
其他部分不變,我們對(duì)于上述代碼進(jìn)行菱形虛擬繼承,并且加上一句直接訪問(wèn)的代碼(d._a = 100),再次進(jìn)行測(cè)試。
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.B::_a = 1;
d.C::_a = 2;
d._a = 100; //新增的一句代碼
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
首先觀察調(diào)試窗口:我們可以看到創(chuàng)建的d變量的地址。
此時(shí)再讓代碼執(zhí)行d.B::_a = 1這句,通過(guò)內(nèi)存窗口可以看到其變化,但是和菱形繼承的變化位置不同。
再讓代碼執(zhí)行d.C::_a = 2這句,通過(guò)內(nèi)存窗口可以看到其是直接在原來(lái)的位置處改變的,只有一份 _a。
再讓代碼執(zhí)行d._a = 100這句,內(nèi)存窗口變化如下:
再執(zhí)行d._b = 3這條語(yǔ)句。
再執(zhí)行d._c = 4這條語(yǔ)句。
再執(zhí)行d._d = 5這條語(yǔ)句。
有個(gè)疑問(wèn),此處圈住的部分是什么呢?它看起來(lái)像一個(gè)地址(X64環(huán)境下),那么其是存儲(chǔ)什么的呢?
解釋一下,其確實(shí)為兩個(gè)指針,指向的一張表。這兩個(gè)指針叫虛基表指針,這兩個(gè)表叫虛基表。虛基表中存的偏移量。通過(guò)偏移量可以找到下面的A。
通過(guò)內(nèi)存窗口可以觀察出:繼承的B中存儲(chǔ)了十六進(jìn)制下的28,即十進(jìn)制下的40。對(duì)比下圖,和偏移量相等。
對(duì)于內(nèi)存模型抽象化
2.4總結(jié)
1.通過(guò)虛擬繼承,D類對(duì)象中只有一個(gè)A類的_a,從而解決了數(shù)據(jù)冗余和二義性。
2.菱形虛擬繼承的對(duì)象模型:
每個(gè)繼承對(duì)象中存儲(chǔ)一個(gè)虛基表,虛基類(即上例中的A)放在最下面,成為公共部分。
3.存儲(chǔ)的地址的作用
其為虛基表指針,指向虛基表,虛基表中存儲(chǔ)偏移量,可以找到下面存儲(chǔ)的A。從而方便切片的場(chǎng)景。
4.菱形虛擬繼承也會(huì)改變中間類的結(jié)構(gòu),讓它們的結(jié)構(gòu)和D的結(jié)構(gòu)類似。
這樣是為了防止以下的場(chǎng)景:
D d;
B b;
B& ref = d;
ref = b;
如果不存儲(chǔ)成類似的結(jié)構(gòu),那么找到B類存儲(chǔ)的_a就比較困難。
3.問(wèn)題總結(jié)
1.C++有多繼承,為什么?為什么Java沒(méi)有?
這個(gè)得從C++歷史發(fā)展來(lái)看,在C++發(fā)展史中,在完善面向?qū)ο蟮倪^(guò)程中,祖師爺考慮到了現(xiàn)實(shí)生活中確實(shí)有一部分東西可以繼承多個(gè)類,比如西紅柿既是水果,又是蔬菜,因此C++有了多繼承。而Java在這方面通過(guò)C++的痛苦因此做出了 改變,只允許單繼承。
2.多繼承的問(wèn)題是什么?
多繼承本身沒(méi)有問(wèn)題,但是有多繼承就會(huì)有菱形繼承,而菱形繼承就有許多問(wèn)題。
3.菱形繼承的問(wèn)題是什么,如何解決?
數(shù)據(jù)冗余以及二義性。通過(guò)菱形虛擬繼承解決。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860047.html
4.底層角度如何解決菱形繼承的問(wèn)題(數(shù)據(jù)冗余和二義性)?
菱形虛擬繼承在底層改變了數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu),將虛基類存儲(chǔ)在了最下面,作為公共部分,讓多繼承而來(lái)的父類的數(shù)據(jù)共享,共用一份,而在繼承的那部分中則存儲(chǔ)了虛表指針,指向虛基表,從而得到其中存儲(chǔ)的偏移量,進(jìn)而可以實(shí)現(xiàn)切片時(shí)數(shù)據(jù)的完整性。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860047.html
到了這里,關(guān)于C++:面向?qū)ο蟠罂樱毫庑卫^承的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!