1. 程式人生 > >C++基於物件的程式設計基本概念總結(五)-繼承

C++基於物件的程式設計基本概念總結(五)-繼承

19.繼承

(1) 類與類之間的關係

has-A,包含關係,用以描述一個類由多個“部件類”構成,實現has-A關係用類的成員屬性表示,即一個類的成員屬性是另一個已經定義好的類。

use-A,一個類使用另一個類,通過類之間的成員函式相互聯絡,定義友元或者通過傳遞引數的方式來實現。

is-A,即繼承關係,關係具有傳遞性。

(2) 繼承的相關概念

萬事萬物皆有繼承這個現象,所謂的繼承就是一個類繼承了另一個類的屬性和方法,這個新的類包含了上一個類的屬性和方法,被稱為子類或者派生類,被繼承的類稱為父類或者基類。

單繼承:一個派生類只從一個基類派生。

多重繼承:一個派生類有兩個或者多個基類

基類與派生類的關係:基類是派生類的抽象,派生類是基類的具體化。基類綜合了派生類的公共特徵,派生類則在基類的基礎上增加了某些特徵,把抽象類變成具體的、實用的型別。

(3) 派生類的宣告方式:

Class 派生類名:[繼承方式] 基類名

{  派生類新增加的成員 };

繼承方式包括:公有繼承,私有繼承,保護繼承。不寫此項,則預設為私有繼承。

(4) 構造一個派生類需要完成的工作:

1)從基類接受成員。(可能會造成資料的冗餘)

2)調整從基類接受的成員。例如改變基類成員在派生類中的訪問屬性。覆蓋(Override)是指派生類中存在重新定義的函式,其函數名、引數列、返回值型別必須同父類中的相對應被覆蓋的函式嚴格一致,覆蓋函式和被覆蓋函式只有函式體 (花括號中的部分)不同,當派生類物件呼叫子類中該同名函式時會自動呼叫子類中的覆蓋版本,而不是父類中的被覆蓋函式版本,這種機制就叫做覆蓋。

3)在宣告派生類時增加成員。

(5) 繼承方式及訪問屬性

1) 公用繼承(public)

用公用繼承方式建立的派生類稱為公用派生類。其基類稱為公用基類。

公有繼承的特點是基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的狀態,而基類的私有成員仍然是私有的,不能被這個派生類的子類所訪問。

2) 私有繼承(private)

用私有繼承方式建立的派生類稱為私有派生類。其基類稱為私有基類。

私有繼承的特點是基類的公有成員和保護成員都作為派生類的私有成員,並且不能被這個派生類的子類所訪問。私有基類的私有成員在派生類中稱為不可訪問的成員,只有基類的成員函式可以引用。

3) 保護繼承(protected)

用保護繼承方式建立的派生類稱為保護派生類。其基類稱為保護基類。

保護繼承的特點是基類的所有公有成員和保護成員都成為派生類的保護成員,並且只能被它的派生類成員函式或友元訪問,基類的私有成員仍然是私有的。

基類成員在派生類中的訪問屬性:

繼承方式

基類的public成員

基類的protected成員

基類的private成員

public繼承

仍為public成員

仍為protected成員

不可訪問

Private繼承

變為private成員

變為private成員

不可訪問

Protected繼承

變為Protected成員

變為Protected成員

不可訪問

注意:父類中的private成員依然存在於子類中,但是卻無法訪問到。不論何種方式繼承父類,子類都無法直接使用父類中的private成員。如果需要在派生類中引用基類的某些成員,則應該將基類的這些成員宣告為protected。需要被外界訪問的成員設定為public只能在當前類中訪問設定為private

(6) 繼承的特點

1) 子類擁有父類的所有屬性和方法(除了建構函式和解構函式)。

2) 子類可以擁有父類沒有的屬性和方法。

3) 子類是一種特殊的父類,可以用子類來代替父類。

4) 子類物件可以當做父類物件使用。

(7) 繼承中的構造和解構函式

1)父類的構造和析構

當建立一個物件和銷燬一個物件時,物件的建構函式和解構函式會相應的被C++編譯器呼叫。當在繼承中,父類的構造和解構函式是如何在子類中進行呼叫的呢,C++規定我們在子類物件構造時,需要呼叫父類的建構函式完成對對繼承而來的成員進行初始化,同理,在析構子類物件時,需要呼叫父類的解構函式對其繼承而來的成員進行析構。

在派生類中定義派生類建構函式的一般形式:

派生類建構函式名(基類所需的形參,本類成員所需的形參):基類建構函式名(基類所需引數表)

{派生類中新增成員初始化語句 }

在派生類體外定義派生類建構函式的一般形式:

派生類::派生類建構函式名(基類所需的形參,本類成員所需的形參):基類建構函式名(基類所需引數表)

{派生類中新增成員初始化語句 }

2父類中的構造和析構執行順序

子類物件在建立時,會先呼叫父類的建構函式,如果父類還存在父類,則先呼叫父類的父類的建構函式,依次往上推理即可。父類建構函式執行結束後,執行子類的建構函式。當父類的建構函式不是C++預設提供的,則需要在子類的每一個建構函式上使用初始化列表的方式呼叫父類的建構函式。

解構函式的呼叫順序和建構函式的順序相反。

(8) 從成員函式的角度來講述過載和覆蓋的區別。

成員函式被過載的特徵有:1)相同的範圍(在同一個類中);2) 函式名字相同;3) 引數不同;4) virtual關鍵字可有可無。覆蓋的特徵有:1)不同的範圍(分別位於派生類與基類);2) 函式名字相同;3) 引數相同;4) 基類函式必須有virtual關鍵字。

分別位於派生類與基類的不同的成員函式,只有在函式名和引數個數相同。型別相匹配的情況下才發生同名覆蓋,如果只有函式名相同,不會發生同名覆蓋,而屬於函式過載。隱藏是指派生類的函式遮蔽了與其同名的基類函式,規則如下: 1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual關鍵字,基類的函式將被隱藏(注意別與過載混淆)。2) 如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)。

(9) 含有子物件的派生類建構函式

類的資料成員中還可以包含類物件,如可以在宣告一個類時包含這樣的資料成員:Student s1;// Student是已宣告的類名,s1是Student類的物件這時,s1就是類物件中的內嵌物件,稱為子物件(subobject),即物件中的物件

派生類建構函式的任務應該包括3個部分:

對基類資料成員初始化;

對子物件資料成員初始化;

對派生類資料成員初始化。

定義派生類建構函式的一般形式為:

   派生類建構函式名(總引數表列):基類建構函式名(引數表列),子物件名(引數表列)

{派生類中新增數成員據成員初始化語句}

或者

    派生類建構函式名(總引數表列):基類建構函式名(引數表列),子物件名(引數表列),

內建型別成員初始列表{}

執行派生類建構函式的順序是:

呼叫基類建構函式,對基類資料成員初始化;

呼叫子物件建構函式,對子物件資料成員初始化;

再執行派生類建構函式本身,對派生類資料成員初始化。

執行派生類解構函式的順序是:

1)執行派生類自己的解構函式。

2)對派生類新增加的成員進行清理。

3)呼叫子物件的解構函式,對子物件進行清理。

4)最後呼叫基類的解構函式,對基類進行清理。

(10) 注意:

當基類建構函式不帶引數時,派生類不一定需要定義建構函式,然而當基 類的解構函式哪怕只有一個引數,也要為派生類定義建構函式,甚至所定義的派 生類解構函式的函式體可能為空,僅僅起到傳遞引數的作用

當基類使用預設建構函式時或不帶引數的建構函式時,則在派生類中定義建構函式時,可以省略:基類建構函式名(引數表),此時若派生類不需要建構函式,則可以不定義建構函式。

如果派生類的基類也是一個派生類,則每個派生類只需負責其直接基類的構造,依次上溯。

如果解構函式是不帶引數的,在派生類中是否要定義解構函式與它所屬的基類無關,故基類的解構函式不會因為派生類沒有解構函式而得不到執行,他們各自是獨立的

(11) 多重繼承:

在多重繼承中,派生類的建構函式與單繼承下派生類建構函式相似,它必須負責該派生類所有基類建構函式以及物件成員(如果有的話)建構函式的呼叫。同時,派生類的引數必須包含完成所有基類、物件成員以及派生類中新增資料成員初始化所需的引數。

派生類建構函式執行順序如下:

1)所有基類的建構函式,多個基類建構函式的執行順序取決於定義派生類時所指定的順序,與派生類建構函式中所定義的成員初始化列表的引數順序無關;

2)物件成員的建構函式;

3)派生類本省的建構函式。

加上虛基類後,它的初始化在語法上與一般多繼承的初始化是相同的,但在呼叫建構函式的順序上有點差別。

1)先呼叫虛基類建構函式,然後呼叫非虛基類的建構函式。

2)當同一層有多個虛基類,按照他們的宣告順序呼叫它們的建構函式;

3)當虛基類是由非虛基類派生時,則先呼叫基類建構函式,再呼叫派生類建構函式。

(12) 虛基類:

如果一個派生類有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的派生類中會保留該間接共同基類資料成員的多份同名成員。引入虛基類,使得在繼承間接共同基類的時候只保留一份成員。

現在,將類A宣告為虛基類,方法如下:

class A//宣告基類A

{…};

class B :virtual public A//宣告類B是類A的公用派生類,A是B的虛基類

{…};

class C :virtual public A//宣告類C是類A的公用派生類,A是C的虛基類

{…};

注意: 虛基類並不是在宣告基類時宣告的,而是在宣告派生類時,指定繼承方式時宣告的。因為一個基類可以在生成一個派生類時作為虛基類,而在生成另一個派生類時不作為虛基類。

宣告虛基類的一般形式為:

class 派生類名: virtual 繼承方式 基類名

經過這樣的聲明後,當基類通過多條派生路徑被一個派生類繼承時,該派生類只繼承該基類一次。

需要注意: 為了保證虛基類在派生類中只繼承一次,應當在該基類的所有直接派生類中宣告為虛基類。否則仍然會出現對基類的多次繼承。

虛基類的初始化如果在虛基類中定義了帶引數的建構函式,而且沒有定義預設建構函式,則在其所有派生類(包括直接派生或間接派生的派生類)中,通過建構函式的初始化表對虛基類進行初始化。規定:在最後的派生類中不僅要負責對其直接基類進行初始化,還要負責對虛基類初始化。C++編譯系統只執行最後的派生類對虛基類的建構函式的呼叫,而忽略虛基類的其他派生類(如類B和類C)對虛基類的建構函式的呼叫,這就保證了虛基類的資料成員不會被多次初始化。

(13) 基類與派生類的轉換:

基類與派生類物件之間有賦值相容關係,由於派生類中包含從基類繼承的成員,因此可以將派生類的值賦給基類物件,在用到基類物件的時候可以用其子類物件代替。

1)派生類物件可以向基類物件賦值

可以用子類(即公用派生類)物件對其基類物件賦值。只能用子類物件對其基類物件賦值,而不能用基類物件對其子類物件賦值,因為基類物件不包含派生類的成員,無法對派生類的成員賦值。同理,同一基類的不同派生類物件之間也不能賦值。

2)派生類物件可以替代基類物件向基類物件的引用進行賦值或初始化。

3如果函式的引數是基類物件或基類物件的引用,相應的實參可以用子類物件

4派生類物件的地址可以賦給指向基類物件的指標變數,也就是說,指向基類物件的指標變數也可以指向派生類物件。

(14) 類的組合:在一個類中以另一個類的物件作為資料成員。