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