【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 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 類別的所有屬性的兩個副本,這會導致歧義。

翻譯一下:
:::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 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):

多重繼承定義:一個衍生類同時繼承多個基類。
語法:
```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)