天天看點

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

文章目錄

  • 一. 繼承的概念及定義
    • 1.繼承的概念
    • 2. 繼承的定義
      • 2.1 定義格式
      • 2.2 繼承關系和通路限定符
      • 2.3 繼承後基類成員通路方式的變化
      • 2.4 幾點補充
  • 二. 基類和派生類對象的指派轉換
    • 1. 概念
    • 2. 補充說明
  • 三. 同名父類成員在子類中的隐藏
    • 1. 概念
    • 2. 成員變量的隐藏
    • 3. 成員函數的隐藏
  • 四. 派生類的預設成員函數
    • 1. 派生類的構造函數
    • 2. 派生類的析構函數
    • 3. 派生類的拷貝構造函數
    • 4. 派生類的指派重載
    • 5. 補充說明
  • 五. 繼承與友元
  • 六. 繼承與靜态成員
  • 七. 菱形繼承及菱形虛拟繼承
    • 1. 繼承關系分類
    • 2. 菱形繼承帶來的問題
    • 3. 虛拟繼承
    • 4. 虛拟繼承解決資料備援和二義性的原理
  • 八. 繼承群組合
    • 1. 繼承群組合的差別
    • 2. 什麼時候用繼承?什麼時候用組合?

一. 繼承的概念及定義

比如我們要定義學生類(Student)和老師類(Teacher),作為人這兩個類共有的基本屬性包括姓名,年齡等。寫兩個類就要各自都聲明姓名和年齡這兩個成員變量,搞起來麻煩,能不能單獨寫一個Person類裡面有姓名和年齡的成員變量,讓學生類和老師類繼承下來,就不用單獨地再去聲明姓名和年齡了呢?

1.繼承的概念

繼承(inheritance)機制是面向對象程式設計使代碼可以複用的重要的手段,它允許程式員在保持原有類特性的基礎上進行擴充,增加功能,這樣産生新的類,稱派生類。繼承呈現了面向對象程式設計的層次結構,展現了由簡單到複雜的認知過程。以前我們接觸的複用都是函數複用,繼承是類設計層次的複用。

2. 繼承的定義

2.1 定義格式

下圖我們看到Person是父類,也稱作基類。Student是子類,也稱作派生類

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

繼承後父類的成員(包括成員變量和成員函數)都會成為子類的一部分

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2.2 繼承關系和通路限定符

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2.3 繼承後基類成員通路方式的變化

  1. 基類的private成員在派生類中無論以什麼方式繼承都是不可見的。這裡的不可見是指基類的私有成員還是被繼承到了派生類對象中,但是文法上限制派生類對象不管在類裡面還是類外面都不能去通路它。
  2. 如果基類成員不想在類外直接被通路,但需要在派生類中能通路,就定義為protected。可以看出保護成員限定符是因繼承才出現的。
  3. 總結:基類的私有成員在子類都是不可見。基類的其他成員在子類的通路方式 == Min權限最小的(成員在基類的通路限定符,繼承方式),其中public > protected > private
【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2.4 幾點補充

  • 子類的關鍵字為class時預設的繼承方式是private,為struct時預設的繼承方式是public,不過最好顯示的寫出繼承方式。
    【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合
  • 在實際運用中一般使用都是public繼承,幾乎很少使protetced/private繼承,也不提倡使用protetced/private繼承,因為protetced/private繼承下來的成員都隻能在子類的類裡面使用,實際中擴充維護性不強。

二. 基類和派生類對象的指派轉換

1. 概念

子類對象 可以指派給 基類的對象 / 基類的指針 / 基類的引用。這裡有個形象的說法叫切片或者切割。寓意把子類中屬于基類那部分切過去。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

示範一下

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2. 補充說明

既然子類可以給基類指派,那麼反過來可不可以呢?

基類的指針可以通過強制類型轉換指派給子類的指針。但是必須要求基類的指針是指向派生類對象時才是安全的。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

三. 同名父類成員在子類中的隐藏

1. 概念

在繼承體系中基類和子類都有獨立的作用域。子類和基類中有同名成員時,在子類内部子類成員将屏蔽對基類同名成員的直接通路,這種情況叫隐藏,也叫重定義。(想要通路必須指定基類的類域 基類::基類成員名稱 來顯示通路基類的同名成員)。

顧名思義就是在子類對象中,子類的同名成員隐藏了基類的同名成員,但基類的同名成員依然存在于子類對象中,隻不過直接調用的預設是子類的同名成員,想要調用基類的同名成員,需要專門指定基類的作用域來調用。從内容上看好像子類對基類的同名成員進行了重定義一樣。

2. 成員變量的隐藏

變量名相同,就構成隐藏(重定義)
【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

3. 成員函數的隐藏

函數名相同,就構成隐藏(重定義)
【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

PS:區分成員函數的重定義和函數重載

成員函數的隐藏 函數重載
對象特征 從基類繼承下來的和子類同名的成員函數 所有的同名函數,但要求參數類型不一緻
作用域 兩個函數處在不同作用域:一個在基類,另一個在子類 處在同一作用域
調用規則 子類對象想要調用基類的同名成員函數,需要指定類域 調用時根據傳入的實參類型來比對

四. 派生類的預設成員函數

類有6個預設成員函數,包括構造函數、拷貝構造函數、指派運算符的重載、析構函數等,那麼在派生類中,如何去關聯基類的預設成員函數呢?

1. 派生類的構造函數

派生類的構造函數必須先調用基類的構造函數初始化基類的那一部分成員。如果基類沒有預設的構造函數(不包括編譯器自動生成的預設構造函數),或有顯示調用基類構造函數的需要,則必須在派生類構造函數的初始化清單位置顯示調用基類的構造函數,以完成基類部分成員的初始化。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合
編譯器預設生成的構造函數不能被繼承下來的問題
【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合
為什麼一定要在初始化清單裡顯示調用基類構造函數?

在子類的構造函數的括号内顯示調用基類的構造函數行不行?不行的,要求必須在初始化清單裡顯示調用目的是保證先構造基類成員,再構造子類成員。在括号裡面給值其實已經不是初始化了,而是已經初始化過後的重新指派。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2. 派生類的析構函數

派生類的析構函數會在被調用完成後自動調用基類的析構函數清理基類成員。因為這樣才能保證派生類對象在析構時先清理派生類成員再清理基類成員的後進先出順序,是以我們沒必要顯示調用基類的析構函數。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合
派生類的構造函數和析構函數設計原理

在子類的構造函數中,因為有顯示調用基類構造函數的需求(比如我們可能要傳參來構造基類成員),并且為了保證基類成員先于子類成員之前構造,是以要求基類的構造函數在子類的初始化清單裡調用。

析構函數一般的話都是對象生命周期結束後自動調用,以完成清理工作,沒有傳參顯示調用需要,并且為了保證子類成員先析構,基類成員後析構,是以設計子類的析構函數裡隻用完成自己成員的清理就行,結束後自動調用父類的析構函數。

3. 派生類的拷貝構造函數

同構造函數一樣,派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化,再初始化派生類成員。初始化清單必須顯示調用基類的拷貝構造,不然的話基類成員會通過預設的拷貝構造初始化。

通過下面的例子來說明基類的拷貝構造是否需要顯示調用的問題

case 1

基類有拷貝構造函數,和自己實作的非預設構造函數

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

case 2

基類有拷貝構造函數,和自己實作的預設構造函數

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

總結

對于基類的拷貝構造,同構造函數一樣也需要自己實作,編譯器預設生成的不會繼承下來,而且在派生類的拷貝構造裡一定要通過切片顯示調用。注意構造函數和拷貝構造是函數重載的關系。

4. 派生類的指派重載

同上面的一樣,必須自己實作基類的指派重載,因為編譯器預設生成的不會被繼承到派生類,又因為基類和派生類的指派重載構成了隐藏(因為他們函數名相同都是operator=)是以必須要以聲明基類作用域的方式來顯示調用基類的指派重載。

通過下面的例子來看看基類的指派重載是否會自動調用

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

總結

基類的指派重載也必須通過切片顯示調用,并且要聲明類域,因為基類的指派重載被子類隐藏了。

5. 補充說明

  1. 前面介紹的四個成員函數,除了析構函數,其他的最好都顯示調用基類的成員函數來完成相應的基類成員的處理。
  2. 基類和子類的指派運算符重載因為函數名相同而成隐藏關系,在子類中調用基類的指派重載時必須指定基類類域。
  3. 派生類對象初始化先調用基類構造再調派生類構造。
  4. 派生類對象析構清理先調用派生類析構,結束後再由編譯器自動調基類的析構。

五. 繼承與友元

友元關系不能繼承,也就是說基類友元不能通路子類私有和保護成員。相當于你父親的朋友不一定是你的朋友

class Student;
class Person
{
public:
	// Person類的友元
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};

class Student : public Person
{
protected:
	int _num; // 學号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._num << endl;
}

int main()
{
	Person p;
	Student s;
	// 編譯不通過,不能通路Student::_num
	Display(p, s);
}
           

六. 繼承與靜态成員

基類定義了static靜态成員,則整個繼承體系裡面隻有一個這樣的成員。無論派生出多少個子類,都共用一個static成員執行個體。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

七. 菱形繼承及菱形虛拟繼承

1. 繼承關系分類

單繼承:一個子類隻有一個直接父類時稱這個繼承關系為單繼承

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

菱形繼承:菱形繼承是多繼承的一種特殊情況

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2. 菱形繼承帶來的問題

從上圖可以看出菱形繼承具有二義性和資料備援的問題,在Assistant對象中Person成員就有兩份。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

3. 虛拟繼承

虛拟繼承可以解決菱形繼承帶來的的二義性和資料備援的問題。如下圖的繼承關系,當Student和Teacher繼承Person時使用虛拟繼承,即可解決問題。

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

4. 虛拟繼承解決資料備援和二義性的原理

為了研究虛拟繼承原理,我們給出了一個簡化的菱形繼承的繼承體系,再借助記憶體視窗觀察對象成員的模型。

非虛拟菱形繼承時

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

虛拟菱形繼承時

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

擴充說明

問題:既然虛拟繼承解決了虛基類(就是有多份的重複繼承的那個類)成員的二義性和資料備援問題,把兩個同樣的虛基類成員和到一起算一個。那虛基表(存指向公共基類的偏移量)存在有什麼用呢?

原因:菱形虛拟繼承時,vs編譯器把虛基類成員放到整個對象的尾部,虛基表中存有偏移量,來計算到公有的虛基類對象的位置。存在下面的這種情況,切割發生時,d需要去找出B/C中的屬于A類的成員指派過去。此時靠虛基表确定到A類成員的偏移量然後切割過去。

D d;
B b = d;
C c = d;
           

八. 繼承群組合

1. 繼承群組合的差別

【C++】繼承知識點總結一. 繼承的概念及定義二. 基類和派生類對象的指派轉換三. 同名父類成員在子類中的隐藏四. 派生類的預設成員函數五. 繼承與友元六. 繼承與靜态成員七. 菱形繼承及菱形虛拟繼承八. 繼承群組合

2. 什麼時候用繼承?什麼時候用組合?

符合has - a就用繼承,符合is - a就用組合。如果都可以優先用組合,因為組合的耦合度低,代碼維護性好。

繼續閱讀