继承和多态 1.0 -- 继承概念(is-a、has-a,赋值兼容规则,隐藏重定义)
普通繼承和訪問權(quán)限
當(dāng)一個(gè)繼承沒有虛擬繼承或者是多重繼承時(shí),就是一個(gè)簡單的繼承的時(shí)候,這個(gè)時(shí)候就是一個(gè)普通的繼承。
普通繼承的內(nèi)存空間是:子類的對(duì)象中,包含了父類的成員變量,同時(shí)也可以調(diào)用父類的成員函數(shù)(有些情況不可以,比如訪問權(quán)限和重定義)
訪問權(quán)限:比較繼承方式和父類的成員修飾符 – 二者取權(quán)限低的那個(gè)。這里的訪問權(quán)限,指的是子類在類內(nèi)部訪問父類成員的權(quán)限和子類對(duì)象在類外訪問的權(quán)限。
(總結(jié):一般繼承時(shí),父類用的是public)
問題:沒有合適的構(gòu)造函數(shù)
我們在編程的時(shí)候,經(jīng)常會(huì)遇到一種情況就是,沒有合適的構(gòu)造函數(shù),這個(gè)時(shí)候的可能有以下幾種原因:
- 構(gòu)造函數(shù)的參數(shù)不匹配,比如我們定義的一個(gè)構(gòu)造函數(shù)是沒有參數(shù)的,然后使用類實(shí)例化一個(gè)對(duì)象的時(shí)候,傳遞了一個(gè)參數(shù),這個(gè)時(shí)候沒有合適的構(gòu)造函數(shù);同樣的道理就是,我們定義的構(gòu)造函數(shù)有參數(shù),但是我們使用類實(shí)例化一個(gè)對(duì)象 的時(shí)候,卻沒有傳遞一個(gè)參數(shù),這個(gè)時(shí)候也是沒有合適的構(gòu)造函數(shù)。這里我們引入一個(gè)概念,就是default構(gòu)造函數(shù),這個(gè)意思就是,我們在使用類實(shí)例化一個(gè)對(duì)象的時(shí)候,可以不用傳參數(shù),這種情況可以是,我們定義的構(gòu)造函數(shù)是不帶參數(shù)的,還有一種情況是,我們的構(gòu)造函數(shù)帶參,但是這個(gè)參數(shù)是一個(gè)缺省值。
- 第二種情況就是,我們的子類繼承了父類,這個(gè)時(shí)候,子類在調(diào)用構(gòu)造函數(shù)的時(shí)候,需要合成一個(gè)構(gòu)造函數(shù),就是先調(diào)用父類的構(gòu)造函數(shù),然后再調(diào)用自己的構(gòu)造函數(shù),把這兩個(gè) 合成了一個(gè)構(gòu)造函數(shù),這個(gè)樣子就是可能存在實(shí)例化一個(gè)對(duì)象的時(shí)候,給 父類傳遞的參數(shù) 不正確。
#include<iostream>
using namespace std;
class A
{
public:A(int a):_a(a){}
public:int _a;
};class B : public A
{
public:B(int a):_b(0), A(a) //這一步非常重要,必須要顯式的構(gòu)造書一個(gè)無名的父類的對(duì)象{}
public:int _b;
};int main()
{A a(0);B b(0); //這個(gè)0參數(shù)實(shí)際是父類需要的參數(shù)a = b;return 0;}
上面我們應(yīng)該注意的一個(gè)問題就是,我們需要在子類(B)里面顯式的構(gòu)造出一個(gè)無名的父類對(duì)象
is-a和has-a
public繼承時(shí)一個(gè)接口繼承,是is-a原則
一個(gè)子類就是一個(gè)父類,因?yàn)楦割惖乃袃?nèi)容都可用(is-a)
protected/private繼承是實(shí)現(xiàn)繼承,父類的成員有一部分子類是無法繼承的
一個(gè)子類中有一個(gè)父類,但是部分父類的成員是不可用的(has-a)
總結(jié):這里所說的is-a和has-a僅僅限于繼承的方式上面,就是如果是 public繼承則是is-a,如果是其他兩種繼承方式則是has-a。不用考慮父類里面的訪問限定符。
關(guān)于is-a的設(shè)計(jì):我們知道公有繼承是is-a的關(guān)系,但是我們不能濫用is-a,is-a代表的一種模型是,父類的每個(gè)操作,子類都能夠執(zhí)行,但是我們必須注意的一個(gè)問題就是,父類的有些操作子類是執(zhí)行不了了,我們在設(shè)計(jì)的時(shí)候,還需要考慮現(xiàn)實(shí)的生活,比如我們設(shè)計(jì)了父類:鳥。鳥里面有有一個(gè)函數(shù)是fly,這里有設(shè)計(jì)了一個(gè)子類,企鵝,采用的繼承方式是公有繼承,但是這里顯然 存在了一個(gè)問題,就是企鵝是 不會(huì) fly的,所以上面的設(shè)計(jì)是不合理的。這里我們還應(yīng)該考慮的一個(gè)問題就是,我們應(yīng)該如何設(shè)計(jì)呢,兩種方式,一種是在鳥類中不設(shè)計(jì)fly,然后子類繼承的時(shí)候采用公有繼承,可以自己定義一個(gè)fly函數(shù),還有一種設(shè)計(jì)是,我們在鳥類中定義了fly函數(shù),然后子類繼承的時(shí)候在進(jìn)行重定義或者是重寫,然后企鵝中告訴我們“企鵝不會(huì)飛”。比較上面的兩種方式我們發(fā)現(xiàn),第一種 方式更合理一些,第二種方式顯得更加的冗余了。
再來看一個(gè)例子,我們一般的認(rèn)為是,一個(gè)正方形就是一個(gè)長方形,但是在設(shè)計(jì)長方形的時(shí)候,我們可以設(shè)計(jì)一個(gè)函數(shù),改變面積,此時(shí)函數(shù) 的功能是只增大長度而不增加寬度,但是 我們 的正方形繼承之后,顯然這個(gè)增加面積的函數(shù)對(duì)于正方形這個(gè)類是錯(cuò)誤的,因?yàn)槲覀兊恼叫我嵌x了函數(shù)之后,正方形就不在是正方形了。
綜上:我們在使用公有繼承的時(shí)候,想使用is-a,就要在設(shè)計(jì)父類的時(shí)候,檢查父類的所有操作是否完全符合子類的要求。
賦值兼容規(guī)則 – 必須是公有繼承(is-a)
我們先來看一個(gè)圖,看看普通的繼承內(nèi)存是如何分配的
所謂賦值兼容規(guī)則就是,子類是一個(gè)父類,這個(gè)時(shí)候子類是可以賦值給父類的,這種賦值 包括三種行為,一種是子類的額對(duì)象賦值給父類,一種是子類的指針賦值給父類,還有一種是子類的引用賦值給父類
上面的三種行為均發(fā)生了切片行為,或者叫做是切割行為。
反過來思考一個(gè)問題就是,我們的父類可以賦值給子類嗎,原則上是不可以的,比如A是父類,B是子類,下面的方式不可以的
A a;
B b;
b = a; //錯(cuò)誤
B& rb = a; //錯(cuò)誤
B* pb = &a; //錯(cuò)誤
從內(nèi)存上面我們很好的理解上面的結(jié)果,但是如果我們使用了強(qiáng)制類型轉(zhuǎn)換就可以改變其中的一部分,比如下面的行為是正確的
A a;
B b;
B& rb = (B*)a; //錯(cuò)誤
B* pb = (B&)&a; //錯(cuò)誤
原因很簡單,如果是 對(duì)象賦值的話,父類的內(nèi)存空間要比子類小的,無法完成賦值,但是指針和引用不一樣,指針和引用的大小是沒有變得,指針是四個(gè)字節(jié),只是把類型強(qiáng)制 轉(zhuǎn)化一個(gè)就可以了,但是有人可能會(huì)費(fèi)解的一個(gè)問題就是,為什么引用可以呢,引用不是指針呀,其實(shí)解釋是這個(gè)樣子的,引用的底層實(shí)現(xiàn)其實(shí)也是一個(gè)指針。
重定義 – 把父類成員隱藏起來
什么是隱藏
子類和父類有相同名字的成員(成員變量或者是成員函數(shù)),這個(gè)訪問 子類的成員的時(shí)候,就會(huì)自動(dòng)的把父類的成員隱藏起來(父類是沒有虛函數(shù)的)
如何理解和記憶
父類定義了一個(gè)函數(shù)或者是成員變量,然后在子類中定義了相同的成員函數(shù)或者是變量,這個(gè)時(shí)候就相當(dāng)于把父類的成員 函數(shù)或者是成員變量隱藏起來了。
重定義是方法,隱藏是結(jié)果
(同樣的道理,我們也可以理解虛函數(shù)的重寫,重寫是方法,覆蓋是結(jié)果)
總結(jié)
以上是生活随笔為你收集整理的继承和多态 1.0 -- 继承概念(is-a、has-a,赋值兼容规则,隐藏重定义)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Effective C++ 1.0 --
- 下一篇: 设计1.0 -- iterator 和c