---
title: 'Class 類別繼承 & virtual 類'
disqus: kyleAlien
---
Class 類別繼承 & virtual 類
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
C++ 提供比修改程式碼更好的方法,**類別繼承 (class inheritance)**,而父類別又稱為 **基礎類別**
[TOC]
## 繼承語法
* 公共的繼承語法,Java 使用 `extends` 關鍵字,而 C++ 使用 `:` 符號,格式 `class <子類> : <父類>`
:::success
子類又稱為公用衍生 (public derivation)
:::
```cpp=
// 基類 (父類)
class Fruit {
private:
string descriptor;
int cost;
public:
Fruit(const string descriptor = "", int cost = 10);
~Fruit();
}
// ---------------------------------------------------------------
// 衍生類 (子類)
class Apple : public Fruit {
private:
string color;
public:
// 實作父類的 constructor
Apple(const string color, const string descriptor = "Apple", int cost = 60);
~Apple();
}
```
### 衍生類概念
:::info
基類又稱為 父類,衍生類又稱為 子類
:::
* 如果父類有建構函數,那子 (衍生) 類,**也要有 ==呼叫的父類的建構函數==,並且必須提供繼承建構子的資料給基 (父) 類**
:::warning
* 父類私有,子類就不會繼承到數據 ? No ! 還是會繼承到
**基礎類別的私有部分,會變為衍生類的一部份,但是只能透過基礎類的公用 & 保護成員函數儲存**,父類私有還是私有的,但是能可透過公開成員方法訪問父類成員變數,下圖可以看出
> 
:::
* 嘗試使用衍生類 (子類) 直接操作基類 (父類),編譯時就會報錯,**私有成員一定要透過 public 成員函數訪問 (protected 除外)**
> 同 Java Field 領域範疇
>
> 
* 除了私有成員外,**子類無法呼叫父類的私有方法 (不可以訪問)**
> 同 Java Method 領域範疇
>
> 
### 建構函數 & 析構函數
* **衍生類建構時會 ^1.^ 先建構基類,^2.^ 再建構自己的建構函數;==建構與析構的順序式相反的==**,先解構自身,在解構基類
:::danger
* 衍生類建構函數,必須呼叫基礎類別的建構函數 (初始化列表),**初始化列表必須使用在實作**(自己設定每個成員),使用在宣告會錯誤,**除非直接在宣告中用 ==inline 函數,就可以使用初始化列表==**
:::
1. Class 基類 & 衍生類抽象對象
```cpp=
/**
* 宣告
*/
class Fruit {
private:
string descriptor;
int cost;
public:
Fruit(const string descriptor = "", int cost = 10);
Fruit(const Fruit & fruit);
void setCharge(int charge) {
this->cost = charge;
}
virtual void showInfo();
void info();
virtual ~Fruit();
};
// --------------------------------------------------- 衍生類
class Apple : public Fruit {
private:
const char *color;
public:
Apple(const char *des = "NULL", int howMach = 0, const char *color = "Red");
Apple(const char *color, const Fruit & f);
void info() {
show();
cout << "Color: " << color << endl;
};
~Apple();
};
```
2. Class 基類 & 衍生類定義
```cpp=
/**
* 實作
*/
Fruit::Fruit(const char *descriptor, int howMach) {
cout << "Fruit construct" << endl;
this->howMuch = howMach;
this->descriptor = descriptor;
}
Fruit::Fruit(const Fruit & f) {
cout << "-----Fruit 複製建構函數" << endl;
this->descriptor = f.descriptor;
this->howMuch = f.howMuch;
}
Fruit::~Fruit() {
cout << "-----Fruit 析構" << endl;
}
//"1. "
Apple::Apple(const char *descriptor, int howMach, const char *color)
: Fruit (descriptor, howMach){
cout << "Apple construct" << endl;
this->color = color;
}
//"2. "
Apple::Apple(const char *color, const Fruit & f)
: Fruit(f) { // 呼叫基類的 "複製函數" &
cout << "Apple construct" << endl;
this->color = color;
}
Apple::~Apple() {
cout << "-----Apple 析構" << endl;
}
```
* 必須呼叫基類建構函數,但是不能直接操作 (必須透過初始化列表),**當沒有呼叫基礎類別的建構函數時,會使用 default construct** (建議自己指定)
* 使用初始化列表,呼叫預設[**複製函數**](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA?view#複製建構函數)
3. 使用,觀察建構、解構的順序
```cpp=
/**
* 使用
*/
#include "BaseClass.h"
int main() {
Fruit f("Fruit info", 90); // 基類使用
f.show();
cout<<"\n";
Apple a("Apple info", 100, "Green Apple"); // 衍生類使用
a.info();
cout<<"\n";
Apple r("Red Apple", f); // 呼叫基類複製函數
r.info();
return 0;
}
```
> 可看出它的建購 & 析構順序
>
> 
### 衍生 & 基礎類 關係
* 重要關係,**==基礎類別 reference 可以參考衍生類別物件==**,但**衍生類 reference 不可以參考基礎類別**,**++子類有的東西父類不一定有++**
> reference 可以有 ==ptr *== or ==ref &==
>
> 
1. ***++子類 ref 參考父類++***:(失敗、不允許,因為這是不安全的向下轉型)
```cpp=
/**
* Apple ref 參考 Fruit
*/
int main() {
Fruit f("Fruit info", 90);
f.show();
cout<<"\n";
Apple &a1 = f; // 引用
Apple *a2 = &f; // 指針
a1.info();
a2->info();
return 0;
}
```
**--實作--**
> 
2. ***++父類 ref 參考子類++***:正常賦予,可以安全轉型
```cpp=
/**
* Fruit ref 參考 Apple
*/
int main() {
Apple greenApple("Apple", "Green", 100);
greenApple.showInfo();
Fruit &ref = greenApple; // 引用
ref.showInfo();
Fruit *ptr = &greenApple; // 指針
ptr->showInfo();
return 0;
}
```
**--實作--**
> 
### 向上、向下轉型
* **將衍生類別的 reference 或 ptr,轉換成基礎類別的 reference 或 ptr,稱之為==向上轉型==**,這個規則適合用於公用繼承
```cpp=
Apple apple("red");
// 向上轉型為 Fruit
Fruit * f_ptr = &apple;
Fruit & f_ref = apple;
```
* **將基礎類別的 reference 或指標,轉換為衍生類別的 reference 或 ptr,稱之為 ==向下轉型==**,使用時一定要 **++明確的型態轉換++**,但這種轉換很危險,因為父類沒有子類的成員,使用上要注意
```cpp=
Fruit f;
// 向下轉型為 Apple
Apple * a_ptr = (Apple *)&f;
Apple & a_ref = (Apple &)f;
```
## Class 關係
> C++ 是物件導向對象,它並不能阻止你用類的關係,但是分清楚對於類別設計是非常好的
### 繼承 is-a
* 公用繼承模式是最常見的形式,稱為 is-a 關係,**全名是 is-a-kind-of 關係**;**有++強烈關係++的類才使用 is-a 關係**,因為有可能繼承到自己不需要的成員
:::info
UML 中的 extends 繼承
:::
> 
### 依賴 has-a
* 繼承關係不能表示出 has-a 的關係;**最簡單的方法就是讓內部有 has-a 的成員**,讓這個類產生 [**聚合**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA#關係)關係,就像是午餐內有香蕉
:::info
UML 中的 [**聚合**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA#關係) 關係 ( 1 ~ 多個對象)
:::
> 
### 比喻 is-like-a
* 繼承關係也不能描述比喻關係,就像是`抱枕` & `女友`,**只能取相同別有的特性,設計一個共享類別**,不能說抱枕就是女友,可用 ABC 來設計 (基礎抽象類別)
:::info
UML 中代表有共同抽象類 (abstract) 的衍生類
:::
> 
### 實作 is-implemented-as-a
* 繼承關係也不能描述 `implemented` 關係,**可用 array 製作 stack,但 array != stack**,兩者個關係是都可以儲存,並沒有繼承這麼強烈的關係
:::info
UML 中代表有共同接口類 (interface) 的衍生類
:::
> 
### 依賴 uses-a
* [**依賴關係**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA?view#關係),跟組合有點像,但是組合關係會更重一點,但筷子 & 午餐並無關係 (筷子可以拿來插丸子之類的? 不一定要吃午餐),相對關係只是依賴
:::info
UML 中代表類的 **依賴關係**
:::
> 
## 同名異式的公用繼承
> 以 Java 來說就是有一個公有類,並且該公有類並不是抽象類
:::danger
Virtual 關鍵字不可以用來描述 類 (class)
:::
### 公用繼承 - Virtual 函數
* 同一個函式,在基類 & 衍生類可以有不同的表示法,**關鍵字 ==vitrual==**,**在基類方法使用 vitrual 描述,就可以在++衍生類使用不同的實作++**
:::warning
* 繼承類可以標明為 `public`、`protect`... 等等 描述,**如果沒有標明 `public`,則子類無法使用繼承類的函數 !!**
:::
:::success
* 子類可以不實現 Virtual 函數,可以使用 基類實現的 Virtual 函數
:::
* 程式會使 **用 ==物件型態決定== 要使用哪一個版本 (基類或衍生類) 的函式**
1. Class **抽象宣告**:使用 `Virtual` 關鍵字描述 `showInfo` 函數
```cpp=
/**
* 宣告
*/
#include <iostream>
#include <string>
using namespace std;
class Fruit {
private:
string descriptor;
int cost;
public:
Fruit(const string descriptor = "", int cost = 10);
// 複製函數
Fruit(const Fruit & fruit);
// 自動轉為 inline 函數
void setCharge(int charge) {
this->cost = charge;
}
virtual void showInfo(); // 虛擬函數
virtual ~Fruit();
};
class Apple : Fruit {
private:
string color;
public:
Apple(const string color, const string descriptor = "Apple", int cost = 60);
Apple(const string color, const Fruit & fruit);
virtual void showInfo(); // 虛擬函數
~Apple();
};
```
* 虛擬函數,在宣告時要用指示字 virtual 描述,而**子類 (衍生類) 宣告時也可以加入 virtual function (也可以不用,但這是個好習慣)**
> **virtual 關鍵字只能使用在 ++宣告中++** (Class 內),如同其他指示字
:::info
* 如果 Class 內有 Virtual 函數,那解構函數就要使用 Virtual
:::
2. **Class 定義**:不使用 `Virtual` 關鍵字,直接定義 `showInfo` 函數 (Virtual)
```cpp=
#include "virtual_h.h"
/**
* 定義
*/
Fruit::Fruit(const string descriptor, int cost) {
cout << "Fruit construct" << endl;
this->descriptor = descriptor;
this->cost = cost;
}
Fruit::Fruit(const Fruit & fruit) {
cout << "-----Fruit 複製建構函數" << endl;
descriptor = fruit.descriptor;
cost = fruit.cost;
}
void Fruit::showInfo() {
cout << "Function in class --- " <<
"descriptor : " << descriptor <<
"cost: " << cost << endl ;
}
Fruit::~Fruit() {
cout << "-----Fruit 析構" << endl;
}
// ------------------------------------------------------ 衍生類
Apple::Apple(const string color, const string descriptor, int cost)
: Fruit(descriptor, cost) {
this->color = color;
}
void Apple::showInfo() {
Fruit::showInfo();
cout << "Function in Apple class --- " <<
"color : " << color << endl ;
}
Apple::Apple(const string color, const Fruit & fruit) : Fruit(fruit) {
cout << "Apple construct" << endl;
this->color = color;
}
Apple::~Apple() {
cout << "-----Apple 析構" << endl;
}
```
* 可以看到子類內方法也可以呼叫基類方法,**==呼叫基類方法時要使用範疇運算子`::`,使用範疇運算子後就可以呼叫基類方法==,沒使用範疇運算子會呼叫到自己實現的方法,導致遞迴**
:::info
這也是範疇運算子 `:` 的一個功能,**分辨同名的函數**
:::
3. 測試:查看對於 Virtual Method,是如何判斷要使用基類還是衍生類,看看 C++ 如何通過 **++物件型態++ 來決定呼叫的函式**
```cpp=
/**
* 使用
*/
#include "BaseClass.h"
int main() {
Fruit fruit("Apple", 90); // Fruit 建構函數
fruit.showInfo();
cout << '\n';
// 1. Fruit 建構函數
// 2. Apple 建構函數
Apple greenApple("Apple", "Green", 100);
greenApple.showInfo();
return 0;
}
```
**--實做--**
> 
### Ref & virtual
* 使用 `Refence` or `Pointer` 會配合 virtual 有不同的反應,**有沒有 virtual 會影響到指標如何判斷函數**
| Virtual Funtion | Ref/Ptr 行為 |
| -------- | -------- |
| No | 根據 **指標之型態** |
| Yes | 根據 **指標 ==所參考== 之型態** |
1. Class 抽象定義:定義基類、衍生類有相同的 函數
```cpp=
/**
* 宣告
*/
class Fruit {
public:
...
void info(); // 沒有 Virtual 的函數
};
class Apple : public Fruit {
public:
...
void info(); // 沒有 Virtual 的函數
};
```
* 多宣告了一個 **沒有 virtual 描述的 `info` 函數**,在基類 & 衍生類各自實作,來觀察沒 virtual 指標會如何操作
2. Class 定義:分別定義 基類 (Fruit)、衍生類 (Apple) 有相同的 函數
```cpp=
/**
* 定義
*/
void Fruit::info() {
cout << "Function in Fruit class --- " <<
"descriptor : " << descriptor <<
", cost: " << cost << endl ;
}
void Apple::info() {
cout << "Function in Apple class --- " <<
", color : " << color << endl ;
}
```
3. 測試:將創建的 Apple 指向 Fruit,測試呼叫 info 時,是呼叫到哪個函數,基數還是衍生類 ?
```cpp=
/**
* 使用
*/
#include "BaseClass.h"
int main() {
Apple apple("Green");
cout << "\n";
Fruit &f_ref = apple;
Fruit *f_ptr = &apple;
f_ref.showInfo(); // 有使用 Virtual 描述
f_ptr->showInfo();
cout << "\n";
f_ref.info(); //"2. " 普通函數
f_ptr->info();
return 0;
}
```
* **有使用 Virtual 描述**:根據指標參考型態呼叫到對應實現的函數 (也就是會參考 apple 的實現)
> 
* **無 Virtual 描述**:使用 ptr、ref 找到 **++沒有使用++ virtual function 後會直接根據 ++指標型態++ 決定 function 的實作** (當前指標型態為 Fruit,所以找 Fruit 的實作)
> 
### Array 類
* 在**陣列內部的每個元素都必須相同**
* 上面範例的 `Fruit`、`Apple` 類如果沒有繼承關係,是無法放置同一個 array 內的,**==有公用繼承關係== array 就會視為++同種原素++**
```cpp=
/**
* 使用
*/
#include "BaseClass.h"
static const int ArrayCount = 4;
int main() {
Fruit *myF[ArrayCount]; // 以基類來宣告 Array
for(int i = 0; i < ArrayCount; i++) {
if(i % 2 == 0) {
myF[i] = new Fruit();
} else {
myF[i] = new Apple();
}
myF[i]->showInfo();
}
return 0;
}
```
* 使用動態創建,這是 4 個指標
1. `Fruit *myF[4];`:這是 4 個指標,由於 ++結合 & 優先順序++ **導致 [] 優先於 \***,創建出了 4 個指標
2. `Fruit (*myF)[4];`:這是 1 個指標,用 **() 限制指標先創建 1 個指標**,再指向有 4 個元素的 array
**--實作--**
> 為了方便觀察,把 construct & destruct 的提示改掉
>
> 
## 靜態 & 動態連結
:::info
* 當程式呼叫函數時,要執行哪一塊的可執行碼 ? 這要由編譯程式來回答
將原始程式碼中的函數呼叫,**解釋為執行特定區塊的函數碼 (稱為 ==繫節 binding==)**
:::
* 在 C 語言中較容易,因為每個函數名稱必須都不同
### C++ 連結
* 只要沒有 virtual 修飾,**函數預設為 ++靜態繫結++**
| 連結方式 | 效率 | 使用原因 |
| -------- | -------- | -------- |
| 靜態 | 高 | 效率好 |
| 動態 | 較低 | 自由度較高、但相對會承擔一點處理時間 (搜尋 `vtbl` 虛擬函數列表,下面會說到) |
1. **靜態連結**:
* C++ 中由於有使用函數多載 (同名但引數不同),**編譯程式必須檢查引數並對應函數名,這屬於靜態繫結、早期繫結 (`static binding`、`early binding`)**
```cpp=
class Fruit {
...
public:
void info(); // 靜態連結
}
// --------------------------------------------------------------
void Fruit::info() {
cout << "Function in Fruit class --- " <<
"descriptor : " << descriptor <<
", cost: " << cost << endl ;
}
```
2. **動態連結**:
* C++ 中的虛擬函數 **virtual funcion 是使用動態連繫結、晚期繫結 (`dynamic binding`、`late binding`)**,因為編譯其不知道使用者要選產生何種物件
```cpp=
class Fruit {
...
public:
virtual void showInfo(); // 動態連結
}
// --------------------------------------------------------------
void Fruit::showInfo() {
cout << "Function in class --- " <<
"descriptor : " << descriptor <<
", cost: " << cost << endl ;
}
```
### 傳值呼叫 - 複製函數
* **使用傳值方式,指標會依照引數一起做用向上轉型**
```cpp=
/**
* 使用
*/
void ref_Function(Fruit & f) {
f.showInfo();
}
void ptr_Function(Fruit * f) {
f->showInfo();
}
void value_Function(Fruit f) {
f.showInfo();
}
int main() {
Fruit f("Fruit", 70);
Apple a("Green");
ref_Function(f);
ref_Function(a);
cout << "\n";
ptr_Function(&f);
ptr_Function(&a);
cout << "\n";
value_Function(f); // 傳值呼叫,呼叫複製建構函數
value_Function(a); // 轉為基類,並呼叫複製建構函數
return 0;
}
```
* 使用傳值呼叫,會啟動 [**隱式複製建構函數**](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA?view#複製建構函數)
> 提醒:顯示複製函數是使用 `=` 等號
* 把 **衍生類傳入仍然會強制依照引數複製類型,向上轉型成基類,導致在呼叫 Function 時呼叫成基類**
> 
### 虛擬函數的運作
> 知道虛擬 (Virtual) 函數的運作方式更有助於了解虛擬函數的概念
* 編譯程式處理虛擬函數的一般方法是使用,**在每個 class 物件中加入一個隱藏成員,該成員儲存一個指標,++指向函數位址的陣列++**
> 該陣列稱為 ==**虛擬函數表格**== **++virtual function table++、++vtbl++,這表格內儲存虛擬數在宣告時的位子**
* 基礎類別物件也會有一個指標,指向儲存該類別所有虛擬函數之位址的表格,**如果衍生類別重新定義虛擬函數,則表格儲存新函數的位址**
> 
```cpp=
Apple apple("Red Apple");
Fruit &f = apple; //"1. "
f.show(); //"2. "
```
1. 依循上圖解釋,第一步會尋找到虛擬表格的位置,而 reference 會指向參考的 Apple 位址,而 Apple 虛擬表格位子為 2096
2. 找到 Apple#`show` 函數的位置,此位子在函數定義時重新改寫,所以位址更新 6400 -> 6820,到 6820 處執行 Function
### 虛擬函數注意
* **++建構函數++:不能為虛擬函數**,因為 **==順序會錯誤==**,++衍生類會優先創建++,但是基礎類應該先被創建才對
* **解構函數:一般都為虛擬函數**,除非不做為基礎類別,解構都由外圍先解構再解構基類
* **friend 不能為虛擬函數**,因為 friend 不屬於成員函數
* 當載入不同函數引入時會產生 **遮蔽問題**
```cpp=
/**
* 宣告
*/
class Fruit {
private:
const char *descriptor;
int howMuch;
public:
Fruit(const char *des = "NULL", int howMach = 0);
Fruit(const Fruit & f);
void setCharge(int m) {this->howMuch = m; }; // inline
// 前後不一致,
virtual void showInfo(int a);
virtual ~Fruit();
};
// ------------------------------------------------------------------------
class Apple : public Fruit {
private:
const char *color;
public:
Apple(const char *des = "NULL", int howMach = 90, const char *color = "Red");
Apple(const char *color, const Fruit & f);
// 基類宣告與衍生類宣告不同,編譯器會是目前的類型做出判斷
virtual void showInfo();
virtual ~Apple();
};
```
> 
### Class 回傳基類
* **例外 - 回傳基類**
> 一般來講,虛擬函數在衍生類中必須保持一致,**但當==回傳函數原型 reference 時,可以修改為回傳衍生類==,如果回傳為物件 (實質對象),則不能將基類改為衍生類**
```cpp=
/**
* 宣告
*/
class Fruit {
private:
const char *descriptor;
int howMuch;
public:
...
virtual const Fruit & Make(int howMach); // 1. 新增 Make 函數
void setCharge(int m) {this->howMuch = m; }; // inline
virtual void showInfo() const;
};
class Apple : public Fruit {
private:
const char *color;
public:
...
virtual const Apple & Make(int howMach); // 2. 修改 Make 函數回傳
virtual void showInfo() const;
};
// ---------------------------------------------------------------
/**
* 實作
*/
const Fruit& Fruit::Make(int howMach) {
setCharge(howMach);
return *this;
}
const Apple& Apple::Make(int howMach) {
Fruit::setCharge(howMach);
return *this;
}
// ---------------------------------------------------------------
/**
* 使用
*/
#include "BaseClass.h"
int main() {
Fruit f;
Apple a("Green");
f.Make(200).showInfo();
a.Make(300).showInfo();
return 0;
}
```
1. 回傳類形是 Fruit 類的 reference,**==限定回傳 reference or ptr==,如果回傳類就不行,編譯會報錯**
2. 公開繼承下來的 virtual 函數,回傳若為基類 reference 則改為衍生類 reference
## protected 存取控制
* 使用 protected 描述,代表該成員是**可以被內部衍生類直接改變,但是對外部還是不可訪問的狀態** (一般 private 成員必須透過 public 方法才能訪問改變成員,而 protected 可以打破這個規則)
* **訪問速度會變快 (不用跳轉函數,避免 Stack 累積),帶是相對的破壞了保護機制**
```cpp=
/**
* 宣告
*/
class Fruit {
...
protected:
int howMuch;
public:
...
virtual void showInfo() const;
...
};
class Apple : public Fruit {
private:
const char *color;
public:
...
virtual void showInfo() const;
...
};
/**
* 實作
*/
void Fruit::showInfo() const {
cout << "Fruit Charge: " << howMuch << endl;
}
void Apple::showInfo() const {
// 子類可取得 howMuch 元素
cout << "Apple charge: " << howMuch << endl;
}
/**
* 使用
*/
#include "BaseClass.h"
int main() {
Fruit f;
Apple a("Green");
f.Make(200).showInfo();
a.Make(100).showInfo();
return 0;
}
```
> 將 `howMuch` 變數移動到 protected,原本需要調用基類 `setCharge` & `getCharge` 才能訪問的變數,現在可以直接方問
## ABC (abstract base class)
* 對於有相同特性並且都需要實體自己實作時就使用 ABC,抽取出共同部分並抽象化,C++ 針對此使用 **==純虛擬函數 pure virtual function==**,**並在++純需函數宣告的結尾處有 ==\=0==++**,並且子類一定要實現
> 這類似於 Java 的抽象類
:::danger
* **++==抽象 class 不可實例化、產生物件==++**,只要**內部函數有 =0 的 Function 就代表該類別為抽象類**
> 
:::
* 被宣告出來的純虛擬函數一定要定義,如果沒定義純虛函數編譯時就會出錯
> 
### 抽象函數
:::info
抽象函數的關鍵字是 `virtual` + `=0`
:::
* 下面的例子,**取 `Teacher` & `Student` 共通點創建 Abstruct `Person` class**
1. 抽象 Person 宣告抽象 **`describe` 純虛函數**
```cpp=
#include <iostream>
using namespace std;
class Person {
protected:
const char *name;
int age;
public:
Person(char* name = (char*)"Non");
void setAge(int age) {
this->age = age;
}
int getAge() const {
return age;
}
virtual char* describe() = 0;
virtual void showInfo() {
cout << describe << ", age: " << age << endl;
}
virtual ~Person();
};
// -----------------------------------------------------------------
Person::Person(char* name) : name(name), age(20){
cout << "Person constructor: " << name << endl;
}
Person::~Person() {
cout << "Person deconstructor: " << name << endl;
}
```
* 可以把共用成員使用 protected 包裝,不讓外界訪問,但是對於衍生類卻可以快速訪問
* 一般來說公開繼承的衍生類,它的建構函數引數要先實現基類,但是相反過來同樣可以,如果 **基類有預設函數時,衍生類也必須要有預設函數**
2. 繼承 Person 類,實現純虛擬函數 (**這時必須去除 `=0` 描述,Virtual 可有可無**)
```cpp=
class Teacher : public Person{
public:
char* describe();
public:
Teacher(char* name);
};
class Student : public Person {
char* describe();
public:
Student(char* name);
};
// ----------------------------------------------------------------
Teacher::Teacher(char* name) : Person(name) { // 實現基類建構函數
}
char* Teacher::describe() {
return (char*) "I am Teacher";
}
// ----------------------------------------------------------------
Student::Student(char* name) : Person(name) { // 實現基類建構函數
}
char* Student::describe() {
return (char*) "I am Student";
}
```
:::info
重新定義基類的純虛函數後,就不會再次被子類呼叫,但是 **如果基類有定義函數,衍生類仍可透過範疇運算子呼叫,如果基類沒定義時則不能呼叫**
:::
3. **測試**:
```cpp=
int main() {
Teacher *t = new Teacher{(char*) "Alien"};
t->setAge(35);
t->showInfo()
Person *pt = t; // 可使用基類參考 or 指標衍生類
pt->setAge(35);
pt->showInfo();
delete t;
cout << "\n";
Student *s = new Student{(char*) "Pan"};
s->setAge(20);
s->showInfo();
Person &ps = *s;
ps.setAge(20);
ps.showInfo();
delete s;
return 0;
}
```
> 
### 繼承 & 動態記憶體
* 使用繼承 + 動態記憶體時,同之前提到的 [**動態記憶體**](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA?view#類別-amp-動態記憶體),要特別注意 `new` & `delete` 的時機,並且 **重新定義 ++copy construct++ & ++指定運算子++**
> 注意複製建構函數的深拷貝:`operator=` `複製建構函數`
* 深拷貝舉例:
1. **基類類抽象、實現**:使用 `new char[]`
```cpp=
#include <iostream>
#include <cstring>
using namespace std;
class DynamicUsed {
private:
static int count;
protected:
char* name;
public:
DynamicUsed(char* name);
DynamicUsed(const DynamicUsed & used);
virtual void showMsg() const {
cout << "name: " << name << ", count: " << count << endl;
}
const DynamicUsed& operator = (const DynamicUsed& used);
friend ostream& operator << (ostream& o, const DynamicUsed &used);
virtual ~DynamicUsed();
};
// -----------------------------------------------------------------------
int DynamicUsed::count = 0;
DynamicUsed::DynamicUsed(char* name) {
cout << "DynamicUsed constructor." << endl;
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
count++;
}
DynamicUsed::DynamicUsed(const DynamicUsed &used) {
cout << "DynamicUsed copy constructor." << endl;
this->name = new char[strlen(used.name) + 1];
strcpy(this->name, used.name);
// 物件 + 1
count++;
}
// 這裡不將物件數量 +1
const DynamicUsed& DynamicUsed::operator=(const DynamicUsed& used) {
cout << "DynamicUsed operator= function" << endl;
if(&used == this) {
return *this;
}
// 刪除原先的 name
delete this->name;
// 賦予 name 新的值
this->name = new char[strlen(used.name) + 1];
strcpy(this->name, used.name);
return *this;
}
ostream& operator << (ostream& o, const DynamicUsed &used) {
o << used.name;
return o;
}
DynamicUsed::~DynamicUsed() {
count--;
}
```
2. **衍生類抽象、實現**:
```cpp=
class ChildDynamic : public DynamicUsed {
private:
char* val;
public:
ChildDynamic(char* name, char* val);
friend ostream& operator << (ostream& o, const ChildDynamic &cd);
~ChildDynamic();
};
ChildDynamic::ChildDynamic(char* name, char* val) : DynamicUsed(name) {
this->val = val;
}
ostream& operator << (ostream& o, const ChildDynamic &cd) {
// 向下轉型,觸發基類的 "<<"
o << (const DynamicUsed&) cd << ", value: " << cd.val;
return o;
}
ChildDynamic::~ChildDynamic() {
}
```
3. **測試**:
```cpp=
int main() {
DynamicUsed du((char*) "Alien"); // 呼叫建構函數
cout << du << endl;
du.showMsg();
cout << endl;
DynamicUsed du2 = du; // 複製建構函數
cout << du2 << endl;
du2.showMsg();
cout << endl;
// 產生暫時物件後,呼叫 operator= 符號
du2 = (char*) "Pan"; // 指定建構函數 + operator=
cout << du2 << endl;
du2.showMsg();
cout << endl;
// 衍生類呼叫基類
ChildDynamic child((char*) "Hello", (char*) "World");
cout << child << endl;
child.showMsg();
return 0;
}
```
> 
## Appendix & FAQ
:::info
C++ 真ㄊㄇ的繁瑣
:::
###### tags: `C++` `設計`