摘要:繼承方式繼承方式限定了基類成員在派生類中的訪問權限,包括公有的私有的和受保護的。所以子類給父類引用賦值也是可以的,相當于給子類對象中繼承的父類部分起了別名。如圖成員函數也是如此,當子類與父類具有函數名相同的函數時,還是符合就近原則。
xxxx我們在寫代碼的時候可能會出現這樣的情況,就是,我們在定義各種類的時候,可能不同的類中會出現大量完全相同的重復成員,這樣就造成了一定的代碼冗余。舉一個例子
無論是Student還是Teacher,他們作為人,姓名年齡都是他們的基本信息,如果每一個類都重復這些成員變量,這就導致了代碼的冗余。當不同的類有相同的基本屬性,也就意味著有相同的成員。為此,就設計出了繼承來解決這個問題
xxxx繼承(Inheritance)可以理解為一個類從另一個類獲取成員變量和成員函數的過程。這是一種很重要的代碼復用手段,繼承能夠使類在原有特性上進行擴展。繼承是類在設計層次上的一種復用。
我先直接給出一個樣例:
這就是基本的語法,還是比較容易記住的。
xxxx繼承方式限定了基類成員在派生類中的訪問權限,包括 public(公有的)、private(私有的)和 protected(受保護的)。
xxxx繼承方式也可以不寫,使用默認的。class類默認的繼承方式是private,struct類默認繼承方式是public。
xxxx一直沒有整理過訪問限定修飾符作用,現在我們已經學完了它的作用,借此機會將其整理一下,讓大家能夠深刻理解。
public(公開):能夠使成員在類內類外都可以被直接訪問
protected(保護):能夠使成員只能在類內被訪問,而不能在類外被直接訪問
private(私有):能夠使成員只能在類內被訪問,而不能在類外被直接訪問
只有public才能在類外被直接訪問,但是,三種都可以在類內被直接訪問
繼承方式/基類成員 | public成員 | protected成員 | private成員 |
---|---|---|---|
public繼承 | public | protected | 不可見 |
protected繼承 | protected | protected | 不可見 |
private繼承 | private | private | 不可見 |
(1)我們發現,繼承之后成員的訪問權限 = min{父類成員訪問權限,繼承方式}
(2)不可見:在用子類訪問時,在子類內、外都不可以訪問到。但是可以通過父類訪問。舉例
(3)這就體現出了protected的作用,單純考慮類中的作用,protected和private并沒有什么大的區別,但是在繼承這里,private會使子類對成員不可見,但是protected只會讓類外不可直接訪問,子類中還是可以直接訪問
(4)在實際應用中,幾乎不會見到把父類的成員設置為private的現象。
我們先看一個現象
xxxx我們在這里發現,子類可以賦值給父類,但是父類不可以賦值給子類。這里就要介紹一個概念,就是**基類/派生類對象賦值兼容(切割)**下面節省,都叫切割
xxxx什么是“切割”?我們來看個圖
xxxx剛剛我們講到的是對象賦值給對象,我們只能將子類對象內容賦值給父類對象。
xxxx但是除了這種“對象<>對象之間的”我們還有別的方法
同上述類似,傳遞指針時,也只能將子類的地址傳給父類的指針,此時該指針解引用只能訪問到父類所含有的內容。
由于引用的底層就是指針,所以指針能夠完成的,引用一般也可以。所以子類給父類引用賦值也是可以的,相當于給子類對象中繼承的父類部分起了別名。此時父類的引用只能訪問父類的成員。
xxxx我們都知道,在同一個作用域中,不可以定義相同的變量和函數(函數重載除外!!),但是在不同的域里確實可以存在的。那么在繼承中,子類能否定義一個與父類相同的成員?
xxxx其實還是比較簡單的,因為不同的類是屬于不同的作用域,因此是可以定義重名成員的。例如:
xxxx雖然可以定義,但是使用的時候,我們是使用的哪一個呢?我們可以驗證一下:
我們發現,A類的對象使用了A類自己的對象(這一點是很正常的)
B類的對象使用了子類的,并沒有使用父類的。其實,這一點也很好解釋,那就是,作用域一直是保證就近原則,在B類中有自己的_a,那么就會首先使用自己的,而不是父類的。
xxxx如果此時我們需要在子類對象中使用父類的同名的變量,我們就需要借助域作用限定符來指定作用域,從而訪問到我們所需的哪一個變量。如圖:
xxxx成員函數也是如此,當子類與父類具有函數名相同的函數時,還是符合就近原則。但是這里就要介紹一個概念就是隱藏/遮蔽
xxxx如果派生類中的成員((包括成員變量和成員函數))和基類中的成員重名,那么就會遮蔽從基類繼承過來的成員。所謂遮蔽,就是在派生類中使用該成員(包括在定義派生類時使用,也包括通過派生類對象訪問該成員)時,實際上使用的是派生類新增的成員,而不是從基類繼承來的。舉個例子:
xxxx但是,如果我們就是想要讓子類對象使用父類中的同名對象呢?**域作用限定符指定!!**如圖:
xxxx這個問題其實也是許多學習者困惑的地方,我也是仔細看了C語言中文網的詳細解釋才發現了這個問題!
xxxx函數重載是指在同一個作用域中,函數名相同,參數列表不同的函數之間形成函數重載
xxxx但是在遮蔽中,我在上面闡述概念的時候,1、沒有拋開子類和父類(因此不屬于同一個作用域)2、并沒有提及參數的問題,只有函數名相同,并沒有對參數提任何要求~
xxxx這就是繼承中作用域的講解!
xxxx我們先來看一個現象
xxxx我們發現,在子類中無法初始化父類的成員變量,爆出“XXX不是基或成員”、
xxxx但是我們再看一個場景
xxxx我們發現,當我們創建一個子類對象的時候,在調用構造函數的初始化列表時候,編譯器會先自動調用父類的默認構造函數再去初始化子類的剩余新增內容有個大前提,就是父類必須要默認構造函數,這樣編譯器才會自動調用。其實這就表明,在子類中將父類的內容看做一個整體,要去初始化父類的內容,就要直接去調用父類的構造函數整體初始化。就好像我們在子類中聲明了一個Person這個自定義類型的成員變量一樣。(構造函數對于自定義類型會自動調用它的構造函數初始化)
xxxx但是,如果父類沒有默認構造函數,就需要我們顯示調用構造函數,方法如下:
xxxx其實拷貝構造跟構造函數是幾乎一樣的。如果有默認的拷貝構造,那子類中的拷貝構造會直接調用默認的拷貝構造函數,如果沒有,就需要我們去顯示調用。
xxxx但是細心的同學會發現為啥我們去拷貝構造Person,你給它傳了一個Student的對象??
xxxx記不記得剛剛提到的“切割”,父類對象是可以接受子類對象的賦值的,在這里就有這樣非常好的應用!!
xxxx賦值重載與上面類似,也需要在子類賦值重載中調用父類的賦值重載。如圖:
注:我們去跑這個賦值重載的代碼一定會有一個bug,其實也不是很難找出,但是我認為不少人還是會小掉進這個溝里,才能再爬出來
還記不記得剛剛講的“遮蔽”問題,子類,父類都有operator=這個函數,所以就產生了“遮蔽”,這就導致了,我們會調用子類的operator=,就會一直重復無限調用子類的operator=,發生StackOverFlow(棧溢出)。
xxxx想要解決這個問題就要指明作用域
xxxx我們按照之前的觀點,我們要析構,就要顯示調用父類的析構函數,加入我們先這樣進行操作,看看會發生什么。。。
我們發現,他報錯了,原因是,在編譯器處理下,所有析構函數都會被處理成一個變量名:destroy()。所以又會出現“遮蔽”的問題,所以我們就要指定作用域了!!
xxxx更改后:
xxxx但是,我們又發現了一個問題,就是為啥會先后調用兩次父類的析構?那我們再試一下,如果我們不去顯示調用父類析構會發生什么。。。
xxxx我們發現,這樣感覺就好多了,正常了,子類和父類都只調用了一次析構函數。而且是先析構子類,再析構父類,這與我們剛剛講解的構造函數完全一致,因為在構造函數中,編譯器就會在初始化列表階段自動調用(先顯示調用)父類的構造,再去構造子類剩下的內容。由于棧的FILO特性,先構造的后析構,這里是完全吻合的。
xxxx因此,我們又得出結論對于析構函數,我們不需要顯示調用父類析構函數,會在子類析構函數結束時自動調用父類的析構函數!!
單繼承:一個子類只有一個直接父類
多繼承:一個子類有兩個或兩個以上的直接父類
xxxx菱形繼承不好解釋,直接看圖就可以看明白
菱形繼承有兩個很明顯的問題:1、數據冗余。2、二義性
xxxxD類繼承了B和C,但是B和C都繼承了A,所以相當于D類中有兩份A的數據,這兩份數據都要儲存在D類中,就會導致不必要的空間浪費。
xxxx當我們去給A類中_a賦值的時候,編譯器不知道是給B類繼承的A的_a賦值還是C類中的,就會產生歧義。
但是這個問題我們可以通過域作用限定符來限定作用域。例如:
xxxx其實二義性還不是一個大問題,至少我們還有方法去解決,但是這個數據冗余的問題就是底層方面的了,我們是無法解決的。所以就出現了虛繼承來解決這個問題。
xxxx在B和C類中添加virtual關鍵字來實現虛繼承就可以解決這個問題。
xxxx到底編譯器底層是怎樣實現用virtual解決數據冗余的呢?(數據冗余與二義性其實是一回事,當數據冗余解決了,二義性自然也就解決了!!)
沒加virtual時
我們很容易看到,B類與C類是完全獨立的,所以是有兩份A類(B與C的前后順序與繼承順序有關)
加virtual時
xxxx我們把這個奇怪的數字作為地址查詢,得到以下結果
對比&D的到的圖,我們發現20和12是有一定意義的!!
20是就是B類相對于公共出來的A類的偏移量(4字節5個位置)
而12同樣也是C類相對于公共出來A類的偏移量(4字節3個位置)
xxxx總之,當使用虛繼承后,就會把冗余重復的類“提取出來”,多帶帶放在一起,這樣就只有一個A類了,就不會出現數據的冗余,也就不會有二義性了!!!
繼承作為面向對象語言的三大特性之一還是非常重要的。但是其實除了這個菱形繼承和虛繼承比較難搞之外,繼承還是比較還理解的,然后重點是把語法和規律稍微記一下,還是比較容易上手的。其實一般情況我覺得也很少會出現菱形繼承的情況。重難點還是接下來的“多態”,我也是一直在啃這個硬骨頭,還是比較難理解的。下面我也會總結出來“多態”的知識,希望大家保持關注!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/122332.html
摘要:入門,第一個這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數式編程語言,它的代碼運行在之上。它通過編輯類工具,帶來了先進的編輯體驗,增強了語言服務。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經到來了,總結過去的 2017,相信小伙們一定有很多收獲...
摘要:入門,第一個這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數式編程語言,它的代碼運行在之上。它通過編輯類工具,帶來了先進的編輯體驗,增強了語言服務。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經到來了,總結過去的 2017,相信小伙們一定有很多收獲...
摘要:入門,第一個這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數式編程語言,它的代碼運行在之上。它通過編輯類工具,帶來了先進的編輯體驗,增強了語言服務。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經到來了,總結過去的 2017,相信小伙們一定有很多收獲...
摘要:大家好,今天屁孩君給大家帶來入門綜合。年,標準委員會發布了語言的第一個國際標準,該標準即為大名鼎鼎的。年,標準委員會發布了一份技術報告,詳細說明了計劃引入的新特性。年月日,經過標準委員投票,標準獲得一致通過。 ...
摘要:關注我,訂閱專欄基礎語言保姆教學,就可以持續讀到我的文章啦本文為萬字長文,滿滿干貨。那么,上面的代碼所運行的結果就是一維數組的使用使用即可以訪問并可以修改,即可讀可寫。 大家好~~~我是開心學編程,學到無極限的@jxwd? 寫在前面: 各位小伙伴還在為C語言的學習而苦惱嘛? 還在為...
閱讀 3005·2021-10-12 10:12
閱讀 3052·2021-09-22 16:04
閱讀 3287·2019-08-30 15:54
閱讀 2602·2019-08-29 16:59
閱讀 2902·2019-08-29 16:08
閱讀 868·2019-08-29 11:20
閱讀 3492·2019-08-28 18:08
閱讀 647·2019-08-26 13:43