C++中的繼承方式與菱形繼承解析
一.什么是繼承?
繼承是類和類之間的關(guān)系,是代碼復(fù)用的重要手段,允許在保持原有類結(jié)構(gòu)的基礎(chǔ)上進行擴展,創(chuàng)建的新類與原有的類類似,只是多了幾個成員變量和成員函數(shù)。
二.繼承的方式
class 派生類名 :繼承方式 基類名 {};
繼承的方式有以下三種:
需要注意的是:
1.若不表明是以何種方式繼承,使用關(guān)鍵字class時默認是私有繼承,使用struct關(guān)鍵字時默認公有繼承
2.上面的不可訪問是指派生類對象不管是在類里還是類外都不能不訪問
三.基類和派生類對象賦值轉(zhuǎn)換
- 在public繼承的情況下,派生類對象可以賦值給基類對象或基類指針或基類的引用,因為這里它是把一個派生類的對象看成是一個基類的對象,把派生類多的那一部分進行切片操作。 因此當一個基類的指針指向一個派生類的對象時,該指針只能訪問基類定義的函數(shù)。(多態(tài)與此相反。如果基類的此函數(shù)聲明為virtual類型,那么基類指針指向子類對象,該指針在調(diào)用該函數(shù)時會調(diào)用子類的函數(shù))
- 當父類的對象賦值給派生類的指針時,這是十分危險的,雖然也能通過強制類型轉(zhuǎn)換來進行賦值,但有可能出現(xiàn)越界的問題。
四.派生類的默認成員函數(shù)
構(gòu)造函數(shù)
(1)派生類的構(gòu)造函數(shù)需要調(diào)用基類的構(gòu)造函數(shù)來初始化基類的那一部分成員。
(2)如果基類沒有默認的構(gòu)造函數(shù)而是自己寫的構(gòu)造函數(shù),則必須在派生類的構(gòu)造函數(shù)初始化列表階段顯 示調(diào)用,如果基類用的是默認構(gòu)造函數(shù),編譯器會自動調(diào)用。
拷貝構(gòu)造
(1)派生類的拷貝構(gòu)造函數(shù)需要調(diào)用基類的拷貝構(gòu)造函數(shù)完成基類的拷貝初始化
(2)默認拷貝構(gòu)造會自動調(diào)用父類的拷貝構(gòu)造。顯式定義子類的拷貝構(gòu)造,編譯器默認調(diào)用的父類構(gòu)造函數(shù)
賦值運算符重載
(1)派生類的operator=需要調(diào)用基類的operator=完成基類的賦值
(2)默認生成的賦值運算符會自動調(diào)用父類的賦值運算符顯式定義子類的賦值運算符, 編譯器不會自動調(diào)用父類的賦值運算符
析構(gòu)函數(shù)
(1)派生類的析構(gòu)函數(shù)會在被調(diào)用完成后自動調(diào)用基類的析構(gòu)函數(shù)清理基類成員。派生類對象析構(gòu)清理先調(diào)用派生類的析構(gòu)再調(diào)用基類的析構(gòu)
(2)父類與子類析構(gòu)構(gòu)成同名隱藏:編譯器底層修改的析構(gòu)函數(shù)的名字,為了支持多態(tài)
(3)父類析構(gòu)在任何情況下編譯器都會自動調(diào)用,不需要顯式調(diào)用
五.同名隱藏(重定義)的問題
"同名隱藏“:在基類和派生類中,如果派生類的函數(shù)和基類的函數(shù)同名,就會有同名隱藏。
如果派生類和基類中有同名成員,子類成員將屏蔽父類對同名函數(shù)的直接訪問。(注意不要與函數(shù)重載混淆)
如果發(fā)生同名隱藏,那么通過對象來調(diào)用同名函數(shù)時,到底是調(diào)用子類還是父類的函數(shù),這個是由指針的類型決定,而不是指針執(zhí)行的對象的類型決定的。
解決同名隱藏問題:通過作用域限定符來訪問 (基類::基類成員)
六.繼承性
1.友元關(guān)系不能繼承,基類友元不能訪問子類私有和保護成員
2.父類定義static靜態(tài)成員,則整個繼承體系里面只有一個這樣的成員
七.菱形繼承
在談菱形繼承之前,需要先了解單繼承和多繼承:
- 單繼承是一個子類只有一個直接父類時稱這個繼承關(guān)系是單繼承
- 一個子類有兩個或兩個以上的直接父類時稱這個繼承關(guān)系為多繼承 繼承中子類中成員的排列次序與繼承次序有關(guān) (也就是說先繼承的類,類中成員的地址越靠下)
菱形繼承:兩個子類同時繼承一個父類,而又有子類同時繼承這兩個子類
缺點:導(dǎo)致數(shù)據(jù)冗余和二義性的問題
解決方法:
- 對造成二義性的屬性使用域訪問限定符,這樣從本質(zhì)上并沒有解決二義性
- 想要解決二義性和數(shù)據(jù)冗余,這就需要用到虛擬繼承。屬性只會生成一份,要調(diào)用它需要使用虛基表指針,虛基表指針指向虛基表,虛基表中存在偏移量,通過偏移量就可以找到共有的那個屬性。
虛擬繼承
一般不會實現(xiàn)虛擬繼承,一般是為了解決菱形繼承中的二義性
class B { public: int _b; }; class D : virtual public B//虛擬繼承 { public: int _d; }; int main() { cout << sizeof(D) << endl;//12 D d; d._b = 1; d._d = 2; return 0; }
在執(zhí)行這段代碼時,首先需要調(diào)用構(gòu)造函數(shù)。從上圖中看到,在構(gòu)造對象期間,編譯器給對象的前4個字節(jié)填充了數(shù)據(jù)。
而這一個過程只能在構(gòu)造函數(shù)期間執(zhí)行。
因此可以推斷應(yīng)該是編譯器默認的構(gòu)造函數(shù)所進行的操作。
cout << sizeof(D) << endl;改語句輸出為12,比想象中的多了4個字節(jié)。
從反匯編中可以看對象時怎樣賦值的。以給_b賦值為例
- 第一個mov語句:把對象的前4個字節(jié)的內(nèi)容獲取
- 第二個mov語句:我們可以把第一個mov語句中獲取得d對象前4個字節(jié)的內(nèi)容向下偏移4個字節(jié)里面的內(nèi)容存到ecx中,具體過成如下圖
- 第三個mov語句:把1賦值到d對象偏移量為8的位置
而個子類自己的_d賦值時一條賦值語句就解決了,而在給基類的成員賦值時通過三條語句來進行的。
看完虛擬繼承,接下來我們把虛擬繼承放到菱形虛擬繼承中來看
class B { public: int _b; }; class C :virtual public B { public: int _c; }; class D : virtual public B { public: int _d; }; class A :public C, public D { public: int _a; }; int main() { cout << sizeof(A) << endl;//輸出結(jié)果為24 system("pause"); return 0; }
為什么它的大小是24個字節(jié)呢?下面給出它的對象模型如下就明白了
到此這篇關(guān)于C++中的繼承方式與菱形繼承解析的文章就介紹到這了,更多相關(guān)C++中的繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows下VScode實現(xiàn)簡單回聲服務(wù)的方法
回聲服務(wù)端可以將客戶端傳來的信息,再原封不動地發(fā)送給客戶端,因而得名 epoch 服務(wù)。接下來通過本文給大家介紹Windows下VScode實現(xiàn)簡單回聲服務(wù)的方法,感興趣的朋友一起看看吧2021-08-08使用Qt實現(xiàn)監(jiān)聽網(wǎng)頁是否響應(yīng)并導(dǎo)出Excel表
Qt導(dǎo)出數(shù)據(jù)到excel,方法有很多,下面這篇文章主要給大家介紹了關(guān)于使用Qt實現(xiàn)監(jiān)聽網(wǎng)頁是否響應(yīng)并導(dǎo)出Excel表的相關(guān)資料,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下2023-11-11詳解C++設(shè)計模式編程中責任鏈模式的應(yīng)用
這篇文章主要介紹了C++設(shè)計模式編程中責任鏈模式的應(yīng)用,責任鏈模式使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系,需要的朋友可以參考下2016-03-03