【C++ 筆記】繼承(Inheritance) - part 22 === 目錄(Table of Contents): [TOC] --- 很感謝你點進來這篇文章。 你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧! OOP 四大特性 --- 首先讓我們複習一下 OOP(物件導向程式設計) 四大特性: 1. 封裝(Encapsulation) 2. 繼承(Inheritance) 3. 多型(Polymorphism) 4. 抽象(Abstraction) 封裝在前面我們已經有學過了,其實就是使用存取修飾子 private 或 protected 來限制資料成員的權限,不被其他程式碼控制。 封裝就跟他的名字一樣,一個被封裝的紙箱,箱子外的人看不見裡面的東西是啥,只有透過打開這個行為(方法)才能進一步看到箱子裡的東西,而這個行為就是限制了外人存取資料成員的權限。 :::success 封裝具體定義:將資料和運算這些資料的方法組合在一個類別(Class)中,並透過存取控制來限制對資料的直接存取。 ::: 封裝主要有以下四種用途跟目的: 1. 資料隱藏:防止外部「直接」存取物件的內部資料,保護資料的完整性和一致性。 2. 介面與實現分離:外部只需透過公開的介面(方法)與物件互動,不必看到內部實現細節。 3. 模組化(modularity):將功能封裝在類別中,使程式結構更清晰,易於維護和擴展。 4. 提高安全性:透過控制存取權限,防止未授權的資料修改。 總之,封裝他主要做的事情就是「隱藏」,將資料隱藏,做安全性措施。實際上可以透過 const、static member、friend 去做控制。 繼承(Inheritance) --- 本篇要說明的 OOP 四大特性之二,就是繼承(Inheritance)。 :::info 定義:繼承是一種機制,允許一個類別(衍生類)基於另一個類別(基類)來定義,並繼承其非私有的成員(包括資料和方法)。 ::: 衍生類(derived-class):從基類衍生出來的。 衍生類我們也可以稱之為「子類別」,而基類也可稱為「父類別」。 簡單來說,繼承的概念可從下例去做理解: 假設動物(Animal)是基類(最根本的類別),而動物可以往下分類,比如說人類也是一種動物,狗、貓都也是動物。 所以可以再細分如下: `Animal()` -> `Human()`、`Dog()`、`Cat()` `Human()`、`Dog()`、`Cat()` 本身都「繼承」了 `Animal()` 的特性。(因為都是動物嘛,既然都是動物,他們一定都會有共同的特徵。) ### 語法(Syntax) --- ```cpp= class derived_class_name : access-specifier base_class_name { // body .... }; ``` derived_class_name:衍生類名稱。 access-specifier:存取修飾子。 base_class_name:基類名稱。 > Note: A derived class doesn’t inherit access to private data members. However, it does inherit a full parent object, which contains any private members which that class declares. by:https://www.geeksforgeeks.org/inheritance-in-c/ 注意:衍生類別不會繼承對私有資料成員的存取權限。但它確實繼承了一個完整的父物件,其中包含該類別宣告的任何私有成員。 ### 範例 1:繼承的使用方法 --- 以下是個範例: ```cpp= #include <iostream> using namespace std; // 基類 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 衍生類 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; Rect.setWidth(5); Rect.setHeight(7); // 輸出物件的面積 cout << "Total area: " << Rect.getArea() << endl; return 0; } ``` 來源:https://www.runoob.com/cplusplus/cpp-inheritance.html 輸出結果: ``` Total area: 35 ``` 以上範例中,基類被定義成 Shape,並設定資料成員 width、height,及兩個 methods:setWidth、setHeight。 而衍生類被定義為 Rectangle,繼承 Shape 的資料成員跟方法。 所以可以在主函數中看到,衍生類能夠使用 Shape 的方法去設定長寬。 繼承的存取控制 --- 以下列表,表示各個型態的類別是否能夠從 public、protected、private 中進行存取。 | 存取類型 | public | protected | private | | -------- | -------- | -------- | -------- | | 同一個類別裡面 | yes | yes | yes | | 衍生類別 | yes | yes | no | | 類別的外面(外部類別) | yes | no | no | 表格來源:https://www.runoob.com/cplusplus/cpp-inheritance.html > 一個衍生類別繼承了所有的基類別方法,但下列情況除外: * 基類別的建構子、解構子和複製建構子。 * 基類別的重載運算子。 * 基類別的 Friend Function。 ### 範例 2:建構子、解構子在繼承的行為 --- 衍生類的建構子會先自動呼叫基類的建構子,然後執行自己的建構子。 以下範例程式碼的輸出結果,就可看出這東西的先後順序。 ```cpp= #include <iostream> using namespace std; class Base { public: Base() { std::cout << "Base constructor\n"; } virtual ~Base() { std::cout << "Base destructor\n"; } }; class Derived : public Base { public: Derived() { std::cout << "Derived constructor\n"; } ~Derived() { std::cout << "Derived destructor\n"; } }; int main() { Base* ptr = new Derived(); delete ptr; return 0; } ``` 輸出結果: ``` Base constructor Derived constructor Derived destructor Base destructor ``` 繼承類型(存取修飾子) --- 我們可以特別指定要繼承 public 還是 private,又或是 protected。 通常在程式設計上普遍會使用 public。 :::info 如果繼承時沒特別指定存取修飾子,預設使用 private。 ::: 以下資訊來自:https://www.runoob.com/cplusplus/cpp-inheritance.html 當使用不同類型的繼承時,遵循以下幾個規則: * 公有繼承(public):當一個類別衍生自「**公有**」基類別時,基類的「**公有**」成員也是衍生類別的「**公有**」成員,基類的「**保護**」成員(protected)也是衍生類的「**保護**」成員(protected),基類的「**私有**」成員(private)不能直接被衍生類存取,但是可以透過呼叫基類的「**公有**」和「**保護**」成員來存取。 * 保護繼承(protected): 當一個類別衍生自**保護**基類別時,基底類別的**公有**和**保護**成員將成為衍生類別的**保護**成員。 * 私有繼承(private):當一個類別衍生自**私有**基底類別時,基底類別的**公有**和**保護**成員將成為衍生類別的**私有**成員。 其實以上這些資訊可從下圖直接明瞭看出: ![image](https://hackmd.io/_uploads/rkyM4MJ3Jl.png) Image Source:https://www.geeksforgeeks.org/inheritance-in-c/ 左欄藍色的為基類成員的存取修飾子,而黑色的為繼承模式。 這個表就是在說當繼承基類的時候,不同存取修飾子會有怎樣的行為。 當衍生類指定為 public 時,基類的 public、protected 都不會受繼承影響而改變,反倒是最後一個基類的 private 會被隱藏起來,不能直接存取。 而後衍生類的 protected,會將基類的 public 變成 protected;private 將首兩項全變成 private。 :::success 我自己是用權限大小來理解,依照這個表格來看就是(權限由小到大):public -> protected -> private。 public 因為權限最小,所以繼承時基類的 public 還是 public,基類的 protected 還是 protected。因為 private 權限最大嘛,所以不管你是外部還是繼承都不能偷看他的資料。 接下來就以此類推。 ::: ### 範例 3:繼承模式 --- ```cpp= #include <iostream> using namespace std; // 基類 class Base { public: int publicVar = 1; protected: int protectedVar = 2; private: int privateVar = 3; }; // 公有繼承 class PublicDerived : public Base { public: void show() { cout << "Public Inheritance:" << endl; cout << "publicVar = " << publicVar << endl; // 還是 public cout << "protectedVar = " << protectedVar << endl; // 還是 protected // cout << "privateVar = " << privateVar; // 無法存取 cout << endl; } }; // 保護繼承 class ProtectedDerived : protected Base { public: void show() { cout << "Protected Inheritance:" << endl; cout << "publicVar = " << publicVar << endl; // 變成 protected cout << "protectedVar = " << protectedVar << endl; // 還是 protected // cout << "privateVar = " << privateVar; // 無法存取 cout << endl; } }; // 私有繼承 class PrivateDerived : private Base { public: void show() { cout << "Private Inheritance:" << endl; cout << "publicVar = " << publicVar << endl; // 變成 private cout << "protectedVar = " << protectedVar << endl; // 變成 private // cout << "privateVar = " << privateVar; // 無法存取 cout << endl; } }; int main() { PublicDerived pub; ProtectedDerived prot; PrivateDerived priv; pub.show(); // cout << pub.publicVar; // 可直接存取 public 成員 // cout << pub.protectedVar; // 無法存取 (因為 protected) prot.show(); // cout << prot.publicVar; // 無法存取 (因為變 protected) priv.show(); // cout << priv.publicVar; // 無法存取 (因為變 private) return 0; } ``` 輸出結果: ``` Public Inheritance: publicVar = 1 protectedVar = 2 Protected Inheritance: publicVar = 1 protectedVar = 2 Private Inheritance: publicVar = 1 protectedVar = 2 ``` 多重繼承(Multiple inheritance) --- 語法: ```cpp= class A { ... .. ... }; class B { ... .. ... }; class C: public A, public B { ... ... ... }; ``` 就是一個衍生類繼承自多個類別,如 class C,同時繼承了 A、B 類別。 ### 範例 4:多重繼承 --- from:[GeeksForGeeks](https://www.geeksforgeeks.org/multiple-inheritance-in-c/) ```cpp= #include<iostream> using namespace std; class A { public: A() { cout << "A's constructor called" << endl; } }; class B { public: B() { cout << "B's constructor called" << endl; } }; class C: public B, public A // Note the order { public: C() { cout << "C's constructor called" << endl; } }; int main() { C c; return 0; } ``` 輸出結果: ``` B's constructor called A's constructor called C's constructor called ``` ### 菱形問題(Diamond Problem) --- 範例 4 的多重繼承,很有可能會造成所謂的菱形問題。 以下是引用自 GeeksForGeeks 的一段話跟圖片: > The diamond problem occurs when two superclasses of a class have a common base class. For example, in the following diagram, the TA class gets two copies of all attributes of Person class, this causes ambiguities. 當一個類別的兩個超類別(層次較高的類別)有一個共同的基類時,就會出現菱形問題。例如,在下圖中,TA 類別獲得了 Person 類別的所有屬性的兩個副本,這會導致歧義。 ![image](https://hackmd.io/_uploads/r1gpxy26yg.png) 翻譯一下: :::info Student、Faculty 都是 Person 的衍生類,而 TA 多重繼承自 Student、Faculty 兩個類別,但是這樣做會在 C++ 裡面發生一個問題叫菱形問題,也就是 TA 會擁有兩套 Person 實例資料的問題。因此實例化 TA 的時候,Person 的建構子會被呼叫兩次,然後解構子也會。 ::: 為了要解決這個問題,所以可以透過一個關鍵字叫做 `virtual` 加在 Student、Faculty 類旁邊(因為被 TA 多重繼承),像是: ```cpp= class Faculty : virtual public Person { // ... }; class Student : virtual public Person { // ... }; class TA : public Faculty, public Student { // ... }; ``` 加了關鍵字 virtual 的繼承也稱為虛繼承(or 虛擬繼承)。 ### 範例 5:虛繼承 --- ```cpp= #include <iostream> using namespace std; class A { public: void display() { cout << "Class A display" << endl; } }; class B : virtual public A { // 使用 virtual 繼承 A public: void showB() { cout << "Class B show" << endl; } }; class C : virtual public A { // 使用 virtual 繼承 A public: void showC() { cout << "Class C show" << endl; } }; class D : public B, public C { // D 同時繼承 B 和 C public: void showD() { cout << "Class D show" << endl; } }; int main() { D obj; obj.display(); // 直接呼叫 A 的 display, 無歧義 obj.showB(); // 呼叫 B 的方法 obj.showC(); // 呼叫 C 的方法 obj.showD(); // 呼叫 D 的方法 return 0; } ``` 輸出結果: ``` Class A display Class B show Class C show Class D show ``` ### 範例 6:帶有資料成員的虛繼承 --- ```cpp= #include <iostream> using namespace std; class A { protected: int value; public: A(int v = 0) : value(v) { cout << "A constructor, value = " << value << endl; } void setValue(int v) { value = v; } int getValue() { return value; } }; class B : virtual public A { public: B(int v = 0) : A(v) { cout << "B constructor" << endl; } }; class C : virtual public A { public: C(int v = 0) : A(v) { cout << "C constructor" << endl; } }; class D : public B, public C { public: D(int v = 0) : A(v), B(v), C(v) { // 明確呼叫 A 的建構子 cout << "D constructor" << endl; } }; int main() { D obj(42); cout << "Value from D: " << obj.getValue() << endl; obj.setValue(100); // 修改共享的 value cout << "Updated value from D: " << obj.getValue() << endl; return 0; } ``` 輸出結果: ``` A constructor, value = 42 B constructor C constructor D constructor Value from D: 42 Updated value from D: 100 ``` 多級繼承(Multilevel Inheritance) --- 多級繼承很容易和多重繼承搞混,所以作者設計下表讓觀念稍微清晰一些: | 特性 | 多級繼承(Multilevel Inheritance) | 多重繼承(Multiple Inheritance) | | -------- | -------- | -------- | | 基類數量 | 每個類別只有一個直接繼承的基類 | 衍生類可有多個直接繼承的基類 | | 結構 | 垂直層次結構(如 A -> B -> C) | 平行結構(如 D 繼承 B 和 C) | | 關係 | 像是「單一血統」的繼承關係 | 像是「多重來源」的繼承關係 | | 複雜性 | 較易,無歧義問題 | 較複雜,有菱形問題 | | 應用 | 動物 -> 哺乳動物 -> 狗 | 學生同時繼承「人」和「學員身分」特性 | | 是否虛繼承 | 否 | 是 | GeeksForGeeks 給多級繼承舉了一個例子,如下: ``` Base class-> Wood, Intermediate class-> furniture, subclass-> table ``` 基類 -> Wood(木頭), 間接類 -> furniture(家具), 子類 -> table(桌子) ![image](https://hackmd.io/_uploads/HJrTXbhp1e.png) Image Source:https://www.geeksforgeeks.org/cpp-multilevel-inheritance/ 語法如下: ```cpp= class A // base class { ........... }; class B : access_specifier A // derived class { ........... } ; class C : access_specifier B // derived from derived class B { ........... } ; ``` ### 範例 7:多級繼承 --- ```cpp= #include <iostream> using namespace std; class Father { public: void fatherMethod() { cout << "Father method" << endl; } }; class Mother { public: void motherMethod() { cout << "Mother method" << endl; } }; class Child : public Father, public Mother { public: void childMethod() { cout << "Child method" << endl; } }; int main() { Child obj; obj.fatherMethod(); // 來自 Father obj.motherMethod(); // 來自 Mother obj.childMethod(); // 來自 Child return 0; } ``` 輸出結果: ``` Father method Mother method Child method ``` 總結 --- 仍有其他的繼承模式,如:Hierarchical Inheritance(層次繼承)、Hybrid Inheritance(混合繼承),但這些都是基於前面的多重、多級繼承,而且概念類似,但礙於篇幅原因所以到此為止。 複習 OOP 四大特性: 1. 封裝(Encapsulation) 2. 繼承(Inheritance) 3. 多型(Polymorphism) 4. 抽象(Abstraction) 繼承定義:繼承是一種機制,允許一個類別(衍生類,Derived Class,或稱子類別)基於另一個類別(基類,Base Class,或稱父類別)定義,並繼承其非私有成員(包括資料和方法)。 如:Animal 是基類,Human、Dog、Cat 是衍生類,這些子類別繼承了 Animal 的共同特徵。 語法: ```cpp= class DerivedClassName : access-specifier BaseClassName { // contents }; ``` * access-specifier:存取修飾子(public、protected、private)。 * 注意:衍生類無法直接存取基類的 private 成員,但繼承了包含這些成員的完整物件。 存取權限表(來自菜鳥教程): | 存取類型 | public | protected | private | | -------- | -------- | -------- | -------- | | 同一個類別裡面 | yes | yes | yes | | 衍生類別 | yes | yes | no | | 類別的外面(外部類別) | yes | no | no | 例外:基類的建構子、解構子、重載運算子和 friend 函數不會被繼承。 繼承模式表(from GeeksForGeeks): ![image](https://hackmd.io/_uploads/SJ6oHZ3p1e.png) 多重繼承定義:一個衍生類同時繼承多個基類。 語法: ```cpp= class A { ... .. ... }; class B { ... .. ... }; class C: public A, public B { ... ... ... }; ``` 多重繼承會遇到的菱形問題:當多個基類共享同一祖先類時,可能導致衍生類擁有該祖先的多份副本,造成歧義。 解決方案:虛繼承(virtual inheritance),使用 virtual 關鍵字讓祖先類只有一份實例。 多級繼承(把他想成是線性的就對了)定義:類別層次結構逐級繼承(如 A -> B -> C)。 多重繼承與多級繼承比較表: | 特性 | 多級繼承(Multilevel Inheritance) | 多重繼承(Multiple Inheritance) | | -------- | -------- | -------- | | 基類數量 | 每個類別只有一個直接繼承的基類 | 衍生類可有多個直接繼承的基類 | | 結構 | 垂直層次結構(如 A -> B -> C) | 平行結構(如 D 繼承 B 和 C) | | 關係 | 像是「單一血統」的繼承關係 | 像是「多重來源」的繼承關係 | | 複雜性 | 較易,無歧義問題 | 較複雜,有菱形問題 | | 應用 | 動物 -> 哺乳動物 -> 狗 | 學生同時繼承「人」和「學員身分」特性 | | 是否虛繼承 | 否 | 是 | 參考資料 --- [C++ Multilevel Inheritance | GeeksforGeeks](https://www.geeksforgeeks.org/cpp-multilevel-inheritance/) [Virtual inheritance - Wikipedia](https://en.wikipedia.org/wiki/Virtual_inheritance) [superclass 並不超級 - Huan-Lin 學習筆記](https://www.huanlintalk.com/2008/02/superclass.html) [Multiple Inheritance in C++ | GeeksforGeeks](https://www.geeksforgeeks.org/multiple-inheritance-in-c/) [Inheritance in C++ | GeeksforGeeks](https://www.geeksforgeeks.org/inheritance-in-c/) [C++ 继承 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-inheritance.html)