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