大家好,我是蘇貝,本篇博客帶大家了解C++的繼承(下),如果你覺得我寫的還不錯的話,可以給我一個贊👍嗎,感謝??
目錄
- 5.繼承與友元
- 6.繼承與靜態成員
- 7.復雜的菱形繼承及菱形虛擬繼承
- 8.繼承的總結和反思
- 9.筆試面試題
5.繼承與友元
友元關系不能繼承,也就是說基類友元不能訪問子類私有和保護成員,就像父母的朋友不是你的朋友一樣
6.繼承與靜態成員
基類定義了static靜態成員,則整個繼承體系里面只有一個這樣的成員。父類靜態成員屬于當前類,也屬于當前類的所有派生類,因此無論派生出多少個子類,都只有一個static成員實例
如果我們想知道A及其派生類實例化了多少對象,就可以定義一個static變量
7.復雜的菱形繼承及菱形虛擬繼承
單繼承:一個子類只有一個直接父類時稱這個繼承關系為單繼承
多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承
多繼承的定義如上圖的Assistant,只需要在原本繼承的基礎上加逗號和其它基類即可
菱形繼承:菱形繼承是多繼承的一種特殊情況。比如助教,對老師來說,他是個學生;對學生來說,他是個老師。因此他有學生和老師兩個身份,就繼承了學生類和老師類
菱形繼承的問題:從下面的對象成員模型構造,可以看出菱形繼承有數據冗余和二義性(后面介紹)的問題。
數據冗余:在Assistant的對象中Person成員會有兩份。
二義性:Student和Teacher類都有_name,無法明確知道訪問的是哪一個
虛擬繼承可以解決菱形繼承的二義性和數據冗余的問題。如上面的繼承關系,在Student和 Teacher的繼承Person時使用虛擬繼承(在繼承公共的基類時用虛擬繼承),即只保存1份Person的成員,就不會造成數據冗余,二義性也就解決了。需要注意的是,虛擬繼承不要在其他地方去使用。
在上面這種菱形繼承中,虛擬繼承用在菱形的腰部B和C,那下圖的用在哪呢?也是B和C,virtual放在有公共基類(A)的類中
虛擬繼承解決數據冗余和二義性的原理:
為了研究虛擬繼承原理,我們給出了一個簡化的菱形繼承繼承體系,再借助內存窗口觀察對象成員的模型。
下面是不加虛擬繼承的菱形繼承
我們可以從內存窗口看出,D里面包含2份A的成員,會導致數據冗余和二義性。因為類D先繼承B,再繼承C,因此D類的對象d中,先出現類B的成員,再是類C的成員,最后是類D的成員。
同理,如果類D先繼承C,再繼承B,那么D類的對象d中,先出現類C的成員,再是類B的成員,最后是類D的成員。
下面我們加上虛擬繼承
我們可以從內存窗口看出,D里面只包含1份A的成員,不會導致數據冗余和二義性。而且這時的B和C類的成員中都不包含本來有的A類成員,并且都存儲了一個地址(0x00677bdc和0x00677be4,機器是小端存儲),我們來看看這2個地址指向的內容
原來地址指向的空間(叫虛基表)里存放的是與A的成員的偏移量(還有其它的內容,這里不做介紹)
為什么要存偏移量?在切片的時候有用。將d賦值給類B的對象bb,就要把D類中B類那部分切來賦值過去,可是這里的B類的成員不包括A類的成員,因此B類保存的地址就能找到與A類的成員的偏移量,就能找到A類的成員,才能最終將B類的成員賦值給bb
多繼承本身沒有問題,但有多繼承就可能導致菱形繼承
總結:實踐中可以設計多繼承,但切記不要設計菱形繼承,因為太復雜,容易出現各種問題
問:下面哪個選項是正確的?
A:p1 == p2 == p3
B:p1 < p2 < p3
C:p1 == p3 != p2
D:p1 != p2 != p3
答案:C
問:下面哪個選項是正確的?
A:p1 == p2 == p3
B:p1 < p2 < p3
C:p2 == p3 != p1
D:p1 != p2 != p3
答案:C
問:下面程序的結果是什么?
類D的對象定義時先走初始化列表,再走函數體,所以”class D”一定在最后。先聲明的先走初始化列表,D類中先聲明的是B類,B繼承A,所以A比B更先聲明,再聲明C,最后D。因此A->B->C->D
8.繼承的總結和反思
1、 很多人說C++語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜。所以一般不建議設計出多繼承,一定不要設計出菱形繼承。否則在復雜度及性能上都有問題。
2、 多繼承可以認為是C++的缺陷之一,很多后來的語言都沒有多繼承,如Java。
3、 繼承和組合
組合是什么?將一個類(如A)作為成員變量放在另一個類中(如B)
public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象。組合是一種has-a的關系。假設B組合了A,每個B對象中都有一個A對象。
適合is-a的關系,如人和學生,就用is-a。適合has-a的關系,如汽車和輪胎,就用has-a。is-a和has-a都可以,如鏈表和棧,就用has-a。即優先使用對象組合,而非類繼承
繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節對子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。
對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為對象的內部細節是不可見的。對象只以“黑箱”的形式出現。組合類之間沒有很強的依賴關系,耦合度低。優先使用對象組合有助于你保持每個類被封裝。
實際盡量多去用組合。組合的耦合度低,代碼維護性好。不過繼承也有用武之地的,有些關系就適合繼承那就用繼承,另外要實現多態,也必須要繼承。類之間的關系可以用繼承,可以用組合,就用組合。
9.筆試面試題
1、 什么是菱形繼承?菱形繼承的問題是什么?
2、 什么是菱形虛擬繼承?如何解決數據冗余和二義性的
3、 繼承和組合的區別?什么時候用繼承?什么時候用組合?
復用的常見體現:
1、 函數邏輯的復用
2、 模板
3、 繼承
4、 組合
好了,那么本篇博客就到此結束了,如果你覺得本篇博客對你有些幫助,可以給個大大的贊👍嗎,感謝看到這里,我們下篇博客見??