# 🔮 多型 Polymorphism
---
- **學習目標:**
- 認識多型的基本觀念與用途
- 學習虛擬函式的使用方法
- 運用多型設計更彈性的程式架構
---
## 🔖 什麼是多型?
- 多型(`Polymorphism`):
- **同一個函式呼叫,可根據物件類型不同而執行不同的動作。**
- 實現方式:
- 使用**虛擬函式 (`virtual function`)**
---
## 📌 虛擬函式 (Virtual Function)
- 使用關鍵字 `virtual` 在基底類別定義函式
- 衍生類別可覆寫基底類別的虛擬函式(override)
----
## 基本語法:
```cpp
class Base {
public:
virtual void show() {
cout << "Base show\n";
}
};
class Derived : public Base {
public:
void show() override { // 覆寫函式
cout << "Derived show\n";
}
};
```
----
## override 解釋
- In C++,當你在衍生類別實作來自虛擬基類(virtual class)的函式時,override 關鍵字不是必須的,但強烈建議使用。
----
### 1️⃣ override 的用途
- 編譯器檢查:
- 如果你打算覆寫(override)基類的虛擬函式,但簽名寫錯(例如參數不同、const 漏掉),加了 override 後編譯器會直接報錯。
- 可讀性:
- 讓程式碼的維護者一眼看出「這是覆寫自基類的虛擬函式」,而不是新的成員函式。
----
### 2️⃣ 不使用 override 的情況
- 功能上:
- 只要函式簽名與基類虛擬函式完全相同,它還是會成功覆寫,執行期會透過 vtable 呼叫到你的版本。
- 風險:
- 如果簽名稍有不同(哪怕只是 const 或 &),編譯器不會幫你檢查 → 你以為覆寫了,實際上只是新增了一個全新的函式,導致多型失效。
----
## 🚩 多型範例
```cpp
Base *bptr;
Base base;
Derived derived;
bptr = &base;
bptr->show(); // Base show
bptr = &derived;
bptr->show(); // Derived show (多型)
```
呼叫的函式依物件真正的型別決定(`動態繫結`)
---
## 📗 多型與指標陣列應用
```cpp
Base* arr[2];
arr[0] = new Base();
arr[1] = new Derived();
for(int i=0; i<2; i++)
arr[i]->show();
// 輸出:
// Base show
// Derived show
```
---
## 🚩 純虛擬函式(Pure Virtual Function)
- 純虛擬函式沒有實作內容,表示必須由衍生類別實作
- 包含純虛擬函式的類別稱為 抽象類別 (Abstract Class)
- 語法:
```cpp
class Shape {
public:
virtual void draw() = 0; // 純虛擬函式
};
```
----
- 純虛擬函式(Pure Virtual Function)在 Java 中又稱 interface
- 可以把它想像成給後續使用者建立的規範或接口
---
## 📌 抽象類別與具體類別
- 抽象類別(Abstract Class):不可直接建立物件
- 具體類別(Concrete Class):可建立物件,必須實作所有純虛擬函式
```cpp
class Circle : public Shape {
public:
void draw() override {
cout << "畫圓形\n";
}
};
Circle c;
c.draw(); // 畫圓形
```
---
| 項目 | 純虛函數 | 抽象類別 |
|:------------ | --------------------------------------- | ------------------------------ |
| 定義 | 沒有實作的虛擬函式,宣告時以 `= 0` 標示 | 至少包含一個純虛函式的類別 |
| 是否可實例化 | 不可實例化 | 不可實例化 |
| 作用 | 定義介面,強制衍生類一定要實作 | 作為介面與基類,提供多型的支援 |
----
| 項目 | 純虛函數 | 抽象類別 |
| ---------------- | ---------------------- |:---------------------------------- |
| 是否必須被實作 | 衍生類必須實作純虛函數 | 衍生類若未實作純虛函數,仍是抽象類 |
| 是否可有函數實作 | 不可有函數體 | 可有普通函數與資料成員 |
---
## 📗 多型設計的好處
1. 增加程式彈性
2. 程式易於擴充
3. 更容易維護與管理不同類型的物件
---
## 🚩 多型經典應用:圖形繪製範例
```cpp
class Shape {
public:
virtual void draw() = 0;
};
class Rectangle : public Shape {
public:
void draw() override {
cout << "畫矩形\n";
}
};
class Triangle : public Shape {
public:
void draw() override {
cout << "畫三角形\n";
}
};
```
---
## 使用方式
```cpp
Shape* shapes[2] = {new Rectangle(), new Triangle()};
for(auto shape : shapes)
shape->draw();
// 輸出:
// 畫矩形
// 畫三角形
```
---
## 📌 虛擬解構子(Virtual Destructor)
- 基底類別應提供虛擬解構子,確保正確解構衍生類別物件
```cpp
class Base {
public:
virtual ~Base() { cout << "Base解構\n"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived解構\n"; }
};
```
----
## 使用方式
```cpp
Base *ptr = new Derived();
delete ptr;
// 輸出:
// Derived解構
// Base解構
```
----
## 沒有虛擬解構子會怎樣?
- 如果基底類別解構子不是 virtual,當你用基底類別指標刪除指向衍生類別的物件時,只會呼叫基底類別的解構子,衍生類別部分不會被釋放
- `→ 資源洩漏或未完成清理。`
----
```cpp
#include <iostream>
using namespace std;
class Base {
public:
~Base() { cout << "Base 解構\n"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived 解構\n"; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // 只呼叫 Base 解構子
}
// 輸出:
// Base 解構
```
----
- 上面變成 function shallowing,只有解構 Base
- -> 記憶體洩漏
----
- Constructor 能不能 virtual?
---
## ⚠️ 多型注意事項
- 透過基底類別指標或參考呼叫虛擬函式才會啟動多型行為
- 基底類別必須提供虛擬解構子,以防記憶體洩漏
---
## 進階 vtable
[C++ Virtual & vtable](/922nZg9UTbSyzl43PNfq_Q)
---
## 🛠️ 課堂練習 (Lab)
- 練習一:
- 實作 Payment system
- A CreditCardPayment is a Payment.
- A PayPalPayment is a Payment.
- A CashPayment is a Payment.
- 練習二:
- 實作抽象類別 Employee,衍生出類別 Manager 和 Worker,透過多型輸出各自職務資訊。
---
## 💡 本週作業
- 將上次作業延伸,完成動物園管理系統
{"title":"🔮 多型 Polymorphism","description":"學習目標:","contributors":"[{\"id\":\"01487228-6720-47a9-875f-2f01b5d455ad\",\"add\":4455,\"del\":187,\"latestUpdatedAt\":1755230948424}]"}