# CPP Lecture5

- __學習基礎++物件導向程式設計++概念 (`OOP`)__
- __認識結構體 (`struct`) 以及如何創建__
- __認識類別 (`class`) 以及如何創建__
- __兩者區別在哪?`OOP` 的邏輯架構?__
---
## Object-Oriented Programming

:::success
==物件導向程式設計== 是將資料與行為包裝成物件,讓程式像模擬現實世界一樣組織與運作
:::
---
### 物件導向程式設計 `OOP`
> Object-Oriented Programming,縮寫為`OOP`
> 是一種程式設計典範,將程式碼組織成 ++相互交互的物件++,
> 每個物件都具有 ++狀態(資料)++ 和 ++行為(方法)++。
在 C/C++ 中,我們可以自定義一個 ++資料型態/類別++:
例如我們在設計一個「學生」模板,就像在
描述一個「真實世界中的學生」。
<br/>
| 現實觀 | 物件觀 |
|-|-|
| 一個學生 | 一個 `Student` 物件 |
| 學生基本資料 | `name`, `age`, `score` 屬性/狀態 |
| 學生的行為 | `study()`, `introduce()` 方法 |
---
### 物件導向程式設計 `OOP` - 概念
> C++ 中,有兩種定義物件型別的方式:
> `struct` & `class`,兩者功能幾乎相同,差別在於預設成員存取權限
1. `類別 (Class)`:像是「學生模板」,定義了++學生型態++有哪些資料與功能。
2. `物件 (Object)`:每個「具體學生」,都是用模板++建構++出來的個體。
3. `屬性 (Attributes)`:學生的「基本資料」,像名字、年齡、成績等狀態。
4. `方法 (Methods)`:學生能做的「事情」,像唸書、自我介紹等行為。
> ++類別++ 就像設計圖,++物件++ 就像用這設計圖蓋出來的房子,
> ++屬性++ 是牆壁與窗戶,++方法++ 是可以開門、開燈的功能。
---
### 物件導向程式設計 - `struct`
==什麼是 struct 組織體?==
- `struct` 是用來封裝一個類別的語法其中之一
- 似 `class`,皆用來封裝相關資料的組合型型別
> (兩者差異會在後面的內容提及)
基本語法:
```cpp[]
/* 依慣例類別名稱字首大寫 */
struct Student { string name; int id; float gpa; }; // 分號結尾
// 結構體 "Student" 包裝了 name、id、gpa 三個屬性
int main() {
Student s1 = {"John", 12345, 3.24}; // int/char 一樣的方式宣告
// s1 是依照 Student 結構體實現出來的一個獨立 "物件"
cout << "學生姓名: " << s1.name << endl; // John,用 dot "成員"
cout << "學號: " << s1.id << endl; // 12345
cout << "GPA: " << s1.gpa << endl; // 3.24
}
```
---
### 物件導向程式設計 - `struct` 成員
<br/>
- 相當於自訂資料型態,囊括 **多個成員 (屬性)**
- 可定義 ++成員函式 (Method)++,非僅是資料集合
使用範例:
```cpp[]
struct Student {
// 屬性 (也可以說是 "成員變數" )
string name; // 姓名
char sex; // 性別
double score; // 學習分數
void introduce() { // 方法 (也可以說是 "成員函式" ) 宣告在結構體內部
cout << "學生: " << name << endl;
cout << "性別是: " << (sex == 'M' ? "Male" : "Female") << endl;
}
};
int main() {
Student s1 = {"Alice", 'F', 95.5}; // s1 物件
s1.introduce(); // 一樣使用 物件 dot 來呼叫內部封裝的成員函式
}
```
---
### 物件導向程式設計 - 巢狀 `struct` 1️⃣
> `struct` 裡面可以宣告另一個 `struct`,這稱為 ++巢狀 `struct`++(`Nested struct`)
<br/>
1. 內部巢狀類型 ⇒
```cpp[]
struct Bank {
string name;
double balance;
};
struct Student {
string name; // 屬性無初始值
int id = 123456; // 預設屬性初始值
double score = 60.0;
// 巢狀 struct:內部類別
struct Birthday {
int year, month, day;
};
Birthday birth; // 使用內部結構型別
Bank studentBankAccount; // 也可以引用外部結構體作為其成員
};
int main() {
Student s1;
s1.name = "Alice";
s1.birth = {2003, 7, 15}; // 訪問內部 struct 的欄位
s1.studentBankAccount = { "Taishin Bank", 34012.04 }; // studentBankAccount 也可以後來再賦予值
cout << "學生 \"" << s1.name << "\" 出生於 "
<< s1.birth.year << "/"
<< s1.birth.month << "/"
<< s1.birth.day << endl;
cout << "學號: " << s1.id << ", " << "分數: " << s1.score << endl;
cout << "學生帳戶銀行: " << s1.studentBankAccount.name << ", "
<< "存款: " << s1.studentBankAccount.balance << endl;
return 0;
}
```
---
### 物件導向程式設計 - 巢狀 `struct` 2️⃣
> `struct` 裡面也可以 ==引用== 另一個 `struct` 作為其屬性 (成員物件)。
<br/>
2. 匿名結構體 (Anonymous struct) ⇒
```cpp[]
struct Student {
string name;
int id = 0; // 預設的初值也可從外部修改
// 匿名 struct,成員會被提升到 Student 層級
struct {
int year, month, day;
} birth; // 注意這邊有名字 birth,仍然是可取用的物件
};
int main() {
Student s2 = {"Bob", 1002, {2004, 8, 20}}; // 依照順序來給定屬性值 name/id/birth(year, month, day)
cout << s2.name << " 生日是 "
<< s2.birth.year << "/"
<< s2.birth.month << "/"
<< s2.birth.day << ", "
<< "新學號: " << s2.id << endl;
return 0;
}
```
---
### ✏️ 練習 1 - struct 一個 "Structure"
<br/>
- 題目要求: 利用 `struct` 建立一個 Structure 型態
- ++屬性++: 名稱 (字串)、層數 (整數)、面積(小數)
、是否有電梯 (布林)、地址 (Address 結構)
- **Address 結構體**: 城市、街道、zipcode
- ++方法++:
- `void describe()` ➤ 輸出所有資訊
- `bool isSkyscraper()` ➤ 回傳布林
- 「摩天大樓」,樓層數 ≥ 40 則 `true`
- `double averageAreaPerFloor()` ➤ 回傳平均面積 (若層數為 0,應避免除 0)
---
### ✏️ 練習 1 - struct 一個 "Structure" 答案
:::spoiler ▼ 💽 測資
```cpp[]
Structure b1 = {
"天際101", // name
101, // floors
300000.0, // area
true, // hasElevator
{"台北市", "信義路五段7號", 110} // address
};
Structure b2 = {
"開心果園",
0,
150.0,
false,
{"屏東縣", "興隆路111號10巷", 90}
};
```
:::
:::spoiler ▼ ✅ 解答
```cpp[]
struct Address {
string city;
string street;
int zipcode;
};
struct Structure {
string name; int floors; double area; bool hasElevator;
Address address; // 巢狀 Address 成員
void describe() {
cout << "🏢 建築名稱: " << name << endl;
cout << "樓層數: " << floors << " 層" << endl;
cout << "總面積: " << fixed << area << " 平方公尺" << endl;
cout << "有電梯: " << (hasElevator ? "是" : "否") << endl;
cout << "地址: " << address.city << ", " << address.street << " (" << address.zipcode << ")" << endl;
}
bool isSkyscraper() { return floors >= 40; }
double averageAreaPerFloor() { if (floors == 0) return area; return area / floors; }
};
```
:::
---
### 物件導向程式設計 - `class`
==什麼是 class 類別?==
- 用來封裝 ++資料++ (Data) 與 ++行為++ (Behavior)
- `struct` 的完整功能版,基礎 `OOP` 模型之一
基本語法:
```cpp[]
class Student { // 可以發現語法與結構體大致相同,差在"權限等級"的設定
private: // 私人權限: 冒號後範圍內的屬性皆是外部無法訪問的,僅供內部模板使用
string name;
int id;
float gpa;
public: // 公開權限,內外皆可自由訪問、竄改
Student(string n, int i, float g) : name(n), id(i), gpa(g) {} // 建構式 (constructor)
string nation = "The State";
void printInfo() {
cout << "Name: " << name << ", Nation: " << nation << ", ID: " << id << ", GPA: " << gpa << endl;
}
};
int main() {
Student s("Amy", 11111, 3.0);
s.nation = "TW"; /* nation 公開屬性能夠訪問 */
// s.gpa = 4.0 /* gpa 私人屬性,屬於 private 不能在外部直接訪問 */
s.printInfo(); // 間接訪問 (由內部訪問) 才能得出 private attributes
return 0;
}
```
---
### 物件導向程式設計 - `class` 權限控制
> ==子類別==: 現有類別擴充所建立的新類別,繼承原有屬性與方法並可新增或改寫。
```cpp[]
class 類別名稱 {
// 依公開等級由高至低 public -> protected -> private
public:
// 公開方法與屬性:可以被類別外部存取
protected:
// 受保護屬性:可供子類/衍生類使用,但外部不可直接存取
private:
// 私人屬性 (class 的預設權限):外部無法直接存取
};
```
```cpp[]
class Student {
private:
string name;
int id;
float gpa;
public:
// Getter (訪問 private 屬性)
string getName() const { return name; }
// Setter (修改 private 屬性)
void setGpa(float newGpa) { gpa = newGpa; }
// const 方法:保證不會修改物件狀態
void printInfo() const {
cout << "Name: " << name << ", ID: " << id << ", GPA: " << gpa << endl;
}
};
```
---
### 物件導向程式設計 - `class` ==protected:==
<br/>
```cpp[]
class Person {
protected: // 可供子類使用,但外部不可直接存取
string name;
public:
Person(string n) : name(n) {} // constructor
void sayHi() {
cout << "Hi, I am " << name << endl;
}
};
class Student : public Person { // Student "繼承" Person 類別,最為其衍生的子類別
private:
int studentId;
public:
Student(string n, int id) : Person(n), studentId(id) {} // constructor
void introduce() {
// name 為 protected,子類別可以使用
cout << "Hello, I'm student " << name << ", ID: " << studentId << endl;
}
};
int main() {
Student s("Alice", 123);
s.sayHi(); // OK:public method
s.introduce(); // OK:public method
// s.name = "Bob"; // ❌ 錯誤:name 為 protected,外部不能存取
}
```
> `protected` 常在繼承 (Inheritance) 中使用,在後續部分提及。
---
### 物件導向程式設計 - `class` 權限修飾子表
<br/>
> 三種權限修飾詞比較表
| 修飾子 | 意義 | 類別內可見 | 子類別可見 | 類別外可見 |
| ----------- | --- | ----- | ----- | ----- |
| `private` | 私有 | ✅ | ❌ | ❌ |
| `protected` | 保護 | ✅ | ✅ | ❌ |
| `public` | 公開 | ✅ | ✅ | ✅ |
---
### 物件導向程式設計 - `class` 封裝概念
<br/>
- ++意涵++: 邏輯/資料/行為合為類別,保護內部資料
- ++實作++: 以 **權限修飾子** 限制存取方式,邏輯包裝
> 以下為三大常見封裝歸類: ++行為/方法++、++設定器++、++存取器++
```cpp[]
class Account {
private:
int balance; // 私人屬性
public:
void deposit(int amt) { balance += amt; } // Behavior
void setBalance(int amt) { balance = amt; } // "Setter"
int getBalance() { return balance; } // "Getter"
};
int main() {
Account myAccount; // 完全不須存取 .balance 而能間接操作受保護的屬性
myAccount.setBalance(100);
myAccount.deposit(1000);
cout << "我的帳戶餘額: " << myAccount.getBalance() << endl;
return 0;
}
```
---
### 物件導向程式設計 - `class` 建 / 解構子
<br/>
- `建構子` Constructor
- 概念: 在物件++被建立時++自動執行的`函式`
- 特性: 函式名需++與類別同名++,無返回型
- 用途: 初始化資院、做準備工作
- `解構子` Destructor
- 概念: 在物件++被釋放時++執行的`函式`
- 特性: 函式名前加 `~`,無參數,無返回值
- 用途: 釋放資源、做收尾工作
```cpp[]
class MyTest { // 最簡易的範例
public:
MyTest() { cout << "Constructor\n"; } // 物件被實踐之初首先被自動呼叫
~MyTest() { cout << "Destructor\n"; } // 物件被銷毀之時立刻被自動呼叫
};
```
---
### 物件導向程式設計 - `class` 建構子
<br/>
#### ‣ 語法與特性 ⇒
- 函式名稱 **必須與類別名稱相同**
- 沒有返回型別(`void` 也不允許寫)
- 支援初始化列表(更有效率)
- 可重載 (`overload`):
- 允許定義多個建構子
- 設定不同參數數量或不同型別
- 可為 `default` (預設)、`explicit` (禁止隱性轉型)
---
### 物件導向程式設計 - `class` 建構子範例1
<br/>
#### `無型態`、`初始化列表` →
```cpp[]
class Student {
private:
string name;
int id;
float gpa;
public:
// 建構子 (Constructor)
Student(string n, int i, float g) // 宣告與類別名稱相同,且不能寫型態
: name(n), id(i), gpa(g) { // 初始化列表寫法 (等於下方註解寫法)
cout << "Student Constructed\n"; // 當物件「被建立」時,此行輸出
}
/*
Student(string n, int i, float g) { // 也可以不使用初始化列表,而是逐項配置
name = id; id = i; gpa = g;
}
*/
};
```
---
### 物件導向程式設計 - `class` 建構子範例2
<br/>
#### `建構子重載` (Overloaded Constructor) →
```cpp[]
class Point {
private:
int x, y;
public:
Point() : x(0), y(0) {} // 無參數建構子
Point(int v) : x(v), y(v) {} // 單一參數輸入: x = y = v 的建構
Point(int xVal, int yVal) : x(xVal), y(yVal) {} // x, y 對應參數輸入
void printPos() { cout << x << ", " << y << endl; }
};
int main() {
Point p1; // 對應到 Point() 建構子
p1.printPos(); // (0, 0)
Point p2(2); // 對應到 Point(int v) 建構子
p2.printPos(); // (2, 2)
Point p3(3, 6); // 對應到 Point(int xVal, int yVal) 建構子
p3.printPos(); // (3, 6)
return 0;
}
```
> 建構子支援重載,根據初始化參數不同可建立不同物件狀態。
---
### 物件導向程式設計 - `class` 建構子範例3
<br/>
#### `default` / `explicit` 建構子 →
```cpp[]
class User {
private:
string name;
public:
User(string n) : name(n) {}
User() = default; // 明確告訴編譯器保留無參建構子
// 讓類別仍能使用 預設無參建構子,即使類別內已有其他建構子。
};
```
```cpp[]
class Meter {
public:
explicit Meter(int x) { cout << x << " meters\n"; } // 明確告訴編譯器禁止隱性轉型
};
int main() {
print(5); // ❌ 編譯錯誤:禁止隱性轉型
// 如果沒有 explicit: 5 會隱式轉型成 Meter(5)
print(Meter(5)); // ✅ 明確轉型才允許
}
```
---
### 物件導向程式設計 - `class` 解構子
<br/>
#### ‣ 語法與特性 ⇒
- 函式名稱為 `~類別名稱`
- 無參數、無返回型別
- 一個類別只能有 **一個解構子**(++不能重載++)
- 編譯器會++自動生成解構子++(若沒寫)
- 釋放資源時機依++物件生命週期++ (如主程式結束)
---
### 物件導向程式設計 - `class` 解構子範例
<br/>
#### `動態記憶體釋放` →
```cpp[]
class Buffer {
private:
int* arr; // 指標陣列,需進行記憶體管理
public:
Buffer(int size) {
arr = new int[size]; // 在記憶體索取一塊大小為 size 的 int 陣列
cout << "Buffer allocated\n";
}
~Buffer() {
delete[] arr; // 隨著物件的生命週期結束,解構式的執行一併釋放占用資源
cout << "Buffer released\n"; // 此行也會在那時輸出
}
};
```
> ==注意:== 一定只有一個解構子,且解構子不能有參數,不能重載
---
### `struct` 與 `class` 的差異
<br/>
> 注意到了嗎 !? `class` 的功能如此廣泛完整,但 ++結構體++ 與 ++類別++ 最主要差異?
| | 結構體 (`struct`) | 類別 (`class`) |
|-|-|-|
| 預設權限 | `public` | `private` |
| 功能支援 | 同 class | 建/解構子、繼承等物件導向功能 |
| 一般用途 | 簡單資料容器、POD 類型 | 複雜類別封裝 |
| 定義方法 | ✅ Enable | ✅ Enable |
---
### ✏️ 練習 2 - 歸類一個 "Structure"
<br/>
- 題目要求: 以 `class` 建立一個 "Structure" 型態
- 屬性: 同 ++練習一++,這次善用「私人屬性」
- **Address 結構體**: 同 ++練習一++
- ++`describe()`++、++`isSkyscraper()`++、++`averageAreaPerFloor()`++
- 建立 ++getter++ 與 ++setter++ 來索取私人成員
> 最後進行屬性隱私檢查,確保外部無法直接訪問私人的成員變數
---
### ✏️ 練習 2 - 歸類一個 "Structure" 答案
:::spoiler ▼ 💽 測試
```cpp[]
int main() {
Address addr = {"台北市", "大安區中正路一段", 99};
Structure s("大安大樓", 39, 20000.0, true, addr);
s.describe();
cout << "是否是摩天大樓?" << (s.isSkyscraper() ? "✅ 是" : "❌ 否") << endl;
cout << "平均每層面積: " << s.averageAreaPerFloor() << " 平方公尺" << endl;
// ✅ 權限測試
// cout << "建築名稱: " << s.name; << endl; // ❌ 編譯錯誤!私有成員不可直接訪問
cout << "建築名稱: " << s.getName() << endl; // ✅ 合法訪問
return 0;
}
```
:::
:::spoiler ▼ ✅ 解答
```cpp[]
#include <iostream>
#include <iomanip>
using namespace std;
// 巢狀結構: Address 不變,保留為 struct(若想也可改成 class 加封裝)
struct Address {
string city;
string street;
int zipcode;
};
class Structure {
private:
string name;
int floors;
double area;
bool hasElevator;
Address address;
public:
// 建構子
Structure(string name, int floors, double area, bool hasElevator, Address addr)
: name(name), floors(floors), area(area), hasElevator(hasElevator), address(addr) {}
// Getter / Setter
string getName() const { return name; }
void setName(const string& n) { name = n; }
int getFloors() const { return floors; }
void setFloors(int f) { floors = f; }
double getArea() const { return area; }
void setArea(double a) { area = a; }
bool getHasElevator() const { return hasElevator; }
void setHasElevator(bool e) { hasElevator = e; }
Address getAddress() const { return address; }
void setAddress(const Address& addr) { address = addr; }
// 方法 describe()
void describe() const {
cout << "🏢 建築名稱: " << name << endl;
cout << "樓層數: " << floors << " 層" << endl;
cout << "總面積: " << fixed << setprecision(2) << area << " 平方公尺" << endl;
cout << "有電梯: " << (hasElevator ? "是" : "否") << endl;
cout << "地址: " << address.city << ", " << address.street << " (" << address.zipcode << ")" << endl;
}
// 是否是高樓大廈
bool isSkyscraper() const {
return floors >= 40;
}
// 平均每層樓面積
double averageAreaPerFloor() const {
if (floors == 0) return area;
return area / floors;
}
};
```
:::
---
### 物件導向程式設計 - `this` 指標
<br/>
==📌 `this` 是什麼?==
`this` 為++內建指標++,每個++非靜態++成員函式中皆可使用
- **`this` 指向呼叫該成員函式的物件本身 (self)**
- `class` 與 `struct` 內成員函式++皆可以++使用
- 用途:
- 區分++區域變數++與++成員變數++同名的情況
- 支援成員函式返回 self (method chaining)
- 進一步延伸到智慧指標 (shared_from_this、fluent API)
---
### 物件導向程式設計 - `this` 指標 情境1️⃣
<br/>
#### * 變數遮蔽 (Name Shadowing) ⇒
```cpp[]
class Student {
private:
int id;
public:
void setId(int id) {
this->id = id; // 左邊是成員變數,右邊是參數
}
};
```
:::info
當區域變數名稱與成員變數相同,需透過 this-> 來明確指定成員變數
:::
---
### 物件導向程式設計 - `this` 指標 情境2️⃣
<br/>
#### * 回傳物件自身 (Method Chaining ) ⇒
```cpp[]
class Counter {
private:
int value = 0;
public:
Counter& increment() {
++value;
return *this; // 將 this 指標所指向的物件「解參考」後回傳,這樣才能回傳物件參考
}
void show() {
std::cout << "Value: " << value << std::endl;
}
};
```
```cpp[]
Counter c; // vlaue initialized to 0
// 鏈式呼叫:
c.increment().increment().show(); // Value: 2
```
---
### 物件導向程式設計 - `this` 指標 情境3️⃣
<br/>
#### * ++結構體++ 同樣支援 `this` 指標 ⇒
```cpp[]
struct Point {
int x, y;
Point& move(int dx, int dy) {
this->x += dx;
this->y += dy;
return *this;
}
};
```
---
### 物件導向程式設計 - `this` 統整
<br/>
| 用法 | 說明 |
|-|-|
| `this` | 指向目前物件的指標:`T*` 類型 |
| `*this` | 解參考取得此物件實體:`T` 類型 |
| `return *this;` | 回傳實體物件自身(非指標) |
| `return this;` | 回傳指向自身的指標(不常用於 Method Chaining)|
| ++靜態成員++函式無 `this` | 因為沒有任何物件實體參與,不屬於特定物件 |
---
### 物件導向程式設計 - 繼承
<br/>
- 概念: `Inheritance`,依識基類建立子類別
- 重複利用基類代碼 擴充改寫為另一類別
- C++ 支援「單一繼承」與「多重繼承」
- 基礎語法:
```cpp[]
class Base {
public:
void sayHi() { cout << "Hi from Base\n"; }
};
class Derived : public Base { // 冒號(接上繼承的權限) + 根基類型名
// public: sayHi() 繼承自 Base
public:
void say() { cout << "sayHi() called from derived: "; sayHi(); }
};
```
---
### 物件導向程式設計 - 繼承建構
<br/>
#### * ++父類別++ 與 ++子類別++ 建構流程 ⇒
```cpp[]
class Base {
public:
Base() { cout << "Base Constructor\n"; }
};
class Child : public Base {
public:
Child() { cout << "Child Constructor\n"; }
};
// 先印出 Base Constructor,再印 Child Constructor
```
---
### 物件導向程式設計 - 繼承的++存取控制++
<br/>
三個熟悉的 **權限修飾詞**:
- `public` / `protected` / `private`
```cpp[]
// 保持父類別存取權限
class Derived1 : public Base { // 公開繼承
// Base 的 public 成員 → 在 Derived1 中仍然是 public
// Base 的 protected 成員 → 在 Derived1 中仍然是 protected
};
// Base 的 public → protected
class Derived2 : protected Base { // 部分繼承
// Base 的 public 與 protected 成員 → 在 Derived2 中都變成 protected
// 外部無法使用 Base 的 public 成員,只有 Derived2 與其子類別能看到
};
// Base 的 public/protected → private
class Derived3 : private Base { // 完全繼承
// Base 的 public 與 protected 成員 → 在 Derived3 中都變成 private
// Derived3 的子類別 也無法存取 Base 成員!
};
```
---
### 物件導向程式設計 - 繼承的++存取控制++
<br/>
==❓base private 成員在哪種情況下子類別可存取?==
- Answer:**`不行!`**
```cpp[]
class Base {
private:
int secret = 42;
protected:
int hidden = 99;
public:
int data = 10;
};
class Derived : Base { /* 沒有寫權限修飾詞,預設就是 private (struct 預設繼承則是 public) */
public:
void show() {
// std::cout << secret; ❌ 編譯錯誤:Base 的 private 成員無法被子類別看到
// std::cout << hidden; ✅ 但由於 protected 成員被轉為 private,這裡還是能訪問
std::cout << data; // ✅ 原本 public 被轉為 private,在這裡仍能用
}
};
class SubDerived : public Derived {
public:
void access() {
// std::cout << data; ❌ Derived 的 data 是 private,這裡就不能用了
}
};
```
> Base 類別的 private 成員從來 ==無法== 被子類別直接訪問,++不論是什麼繼承方式++。
---
### 物件導向程式設計 - 繼承模式1
<br/>
🔹 1. `單一繼承` (Single Inheritance) ⇒
- 指一個子類別**僅繼承自一個父類別**。
```cpp[]
class Base {
public:
void greet();
};
class Derived : public Base {
// 可使用 Base 中的成員
};
```
- 常見於大多數應用場景
- 結構清晰、維護容易
---
### 物件導向程式設計 - 繼承模式2
<br/>
🔹 2. `多重繼承` (Multiple Inheritance) ⇒
- 指一個子類別**同時繼承自多個父類別**。
```cpp[]
class A {
public:
void foo();
};
class B {
public:
void bar();
};
class C : public A, public B {
// 同時擁有 A 與 B 的成員
};
```
- Pros: 可組合多個類別的功能
- Cons: 可能產生命名衝突(同名成員)
---
### 物件導向程式設計 - 多重繼承問題
<br/>
- 多重繼承可能會有 **「鑽石繼承問題」**
```cpp[]
class Base { public: void f(); };
class Left : public Base {};
class Right : public Base {};
class Derived : public Left, public Right {}; // ⚠️ 兩個 Base 成員來源
// 不知道 f() 是哪一份 Base 的? 造成二義性、資料冗餘、甚至錯誤。
```
<br/>
- 🛠️ 解法:使用虛擬繼承 (`virtual`)
```cpp[]
class Base { public: void f(); };
class Left : virtual Base {}; // virtual
class Right : virtual public Base {}; // virtual + 權限修飾
class Derived : public Left, public Right {};
```
> 確保 不管經過多少層繼承,Base 只會保留一份。
> 這樣就能避免 ++鑽石繼承++ 的二義性與重複資料問題。
---
### 物件導向程式設計 - ++虛擬函式++ & ++覆寫++
🔹 1. `虛擬函式`(Virtual Function) ⇒
- 允許子類別「多型化」地重新定義父類別函式
- 使用 `virtual` 關鍵字(修飾詞) 標記
```cpp[]
class Animal {
public:
virtual void speak() {
std::cout << "Animal sound\n";
}
};
```
🔸 2. `覆寫`(Override) ⇒
- 子類可「覆寫」父類的虛擬函式 (加 `override`)
```cpp[]
class Dog : public Animal { // 繼承: Animal 作為父類別
public:
void speak() override { // override 為選擇性的,建議加上(編譯)
std::cout << "Woof\n";
} // 由此一來 Dog().speak() 將會得到 "Woof\n" 輸出
};
```
---
### 物件導向程式設計 - 虛擬函式/覆寫 Note
<br/>
- 🔍 ++選擇性++ (Optional)
- 用 `override` 幫助編譯器檢查是否正確覆寫
- → 參數與回傳型別需一致
- 若無 `virtual`,即使子類定義了同名函式
- → 不會產生動態綁定(運行時多態性)
- 🧠 ++虛擬函式表++ (vtable)
- 每個含虛擬函式的類別會建立一份此表
- ++執行期++ 根據物件類型決定呼叫哪個函式
---
### 物件導向程式設計 - `virtual` 的延伸
<br/>
- `virtual` 函式規則:父類別若定義為 `virtual`
- 子類別即使沒加 `virtual` 也成虛擬函式
- ++純虛擬函式++ 與 ++抽象類別++:
```cpp[]
// 抽象類別:Animal
class Animal {
public:
// 純虛擬函式 => 沒有實作,「子類必須實作」
virtual void speak() = 0;
};
class Monkey : public Animal {
// 沒有實作 speak()
};
int main() {
Monkey m; // ❌ 編譯錯誤:Monkey 是抽象類別,不能實體化
return 0;
}
```
---
### 物件導向程式設計 - 多型/多態性
<br/>
==🔹 什麼是多型?==
- ++**概念**++ 同些介面(函式名) 可以操作 ++不同類型物件++
- ++**分種**++ 時期 (++compile time++ / ++runtime++)
- ++**意義**++
提高程式彈性與可擴充性,支援抽象化操作。
- ++**舉例**++
`draw()` 函式可以對 `Circle`、`Rectangle` 各自執行不同邏輯的 draw 函式執行內容。
---
### 物件導向程式設計 - 多型兩形式
<br/>
| 類型 | 時機 | 方式 |
|-|-|-|
| 1️⃣ **`編譯期多型 (Compiletime)`** | 編譯期 | **函式多載**(Overloading)與**運算子多載**(Operator Overloading) |
| 2️⃣ **`執行期多型(Runtime)`** | 執行期 | **虛擬函式**(Virtual Function) + **指標/參考型別呼叫** |
---
### 物件導向程式設計 - 多型 1️⃣
<br/>
==🔹 編譯期多型 (靜態多型)==
1. 函式多載 (Function Overloading)
```cpp[]
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
```
2. 運算子多載 (Operator Overloading)
```cpp[]
class Point {
int x, y;
public:
Point(int x, int y): x(x), y(y) {}
Point operator+(const Point& p) { // 重新定義了 Point 的 `+` 用法
return Point(x + p.x, y + p.y); // 返回一個座標相加厚的同類別
}
};
```
---
### 物件導向程式設計 - 多型 2️⃣
<br/>
==🔸 執行期多型 (動態多型)==
- 條件: `繼承` + `虛擬函式` + 通過`指標/參考`呼叫
```cpp[]
class Animal {
public:
// 以 virtual 修飾字來允許動態綁定
virtual void speak() { cout << "Animal sound" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
};
Animal* a = new Dog(); // 執行期綁定介面(函式名稱)
// 根據實際物件綁定正確版本
a->speak(); // 輸出: Woof! (指標以 `->` 呼叫成員函式)
```
---
### 物件導向程式設計 - 多型內涵
<br/>
💡 為什麼使用多型`?`
- 開發介面導向架構(如 UI 框架、遊戲引擎)
- 增強模組性與可維護性
- 支援設計模式(如策略模式)
<br/>
⚠️ 注意`!`
- `多型` 只能針對虛擬函式產生作用
- ++非虛擬函式不支援執行期多型++(即使被覆寫)
- 若用值(非指標/參考)傳遞,會發生 ==物件切割==
---
### 物件導向程式設計 - 物件切割問題
<br/>
`Object Slicing`,衍生類別的內容被截斷,範例:
```cpp[]
class Animal {
public:
virtual void speak() { cout << "Animal" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Dog" << endl; }
};
void speak_twice(Animal a) { // ❌ 這裡是傳值
a.speak(); // 呼叫的是 Animal::speak,因為物件已被切割
}
int main() {
Dog d;
speak_twice(d); // ➜ 輸出: Animal,而不是 Dog!
}
```
> `Dog` 物件被當作 `Animal` 複製進函式內,使衍生部分被『切掉』,剩下 `Animal` 本體
---
### 物件導向程式設計 - `vtable` 補充
==🔁 為什麼需要 vtable?==
當建立以下執行期多型時
```cpp[]
Animal* a = new Dog();
a->speak(); // 哪個 speak()?
// a 是 Animal*,而實際物件是 Dog,在執行期要動態決定呼叫哪個函式,這就是「執行期多型」
```
==📦 背後記憶體結構?==
```cpp[]
class Animal {
public:
virtual void speak(); // vtable 會記錄此虛擬函式的位址
};
class Dog : public Animal {
public:
void speak() override; // Dog::speak() 取代 Animal::speak()
};
```
```scss[]
a → [ vptr ] ───────────────▶ 虛擬表 (vtable)
speak() ─────▶ Dog::speak()
/* vptr(virtual pointer)是指向虛擬表的指標,每個具有虛擬函式的物件都會有一個 vptr。 */
```
---
### 物件導向程式設計 - Σ Summary
> 物件導向程式設計(OOP)是一種以物件(Object)為核心的程式設計思維,透過將資料與行為封裝為單一單元,具模組化、可重用性與維護性,大型專案中尤為明顯
- ++**class**++:定義物件的資料與操作
- ++**object**++:根據類別所建立的具體實體
| 四大核心 | Describtion |
|-|-|
| **`封裝` (Encapsulation)** | 把資料與操作方法包裹在類別中,保護內部狀態 |
| **`繼承`(Inheritance)** | 子類別重用父類別功能,並可進行擴充與覆寫 |
| **`抽象`(Abstraction)** | 隱藏實作細節,只暴露必要操作介面 |
| **`多型` (Polymorphism)** | 讓相同操作可作用於不同型別的物件上 |
---
### ✏️ 練習 3 - 形狀類別 "Shape"
- 題目要求: 請設計一個 `Shape` 抽象基底類別,並設計兩個繼承類別 `Rectangle` 與 `Triangle`
- `Shape` 內含純虛擬函式:
- `double area() const` → 計算面積
- `void info() const` → 輸出類別與面積
- `Rectangle` 有兩個邊長 `width`、`height`
- `Triangle` 有底邊 `base` 與高 `height`
- `Shape*` 的指標操作這兩個物件 (多型)
- 實作一個函式 `printShapeInfo(Shape*)`
- 來顯示任意圖形的資訊 (善用成員函式)
---
### ✏️ 練習 3 - 形狀類別 "Shape" 解答
:::spoiler ▼ ✅ 解答
```cpp[]
#include <iostream>
#include <vector>
#include <memory>
#include <iomanip> // for std::fixed and std::setprecision
using namespace std;
// 抽象類別 Shape
class Shape {
public:
virtual double area() const = 0; // 純虛擬函式
virtual void info() const = 0; // 純虛擬函式
virtual ~Shape() = default; // 虛擬解構子
};
// Rectangle 類別
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void info() const override {
cout << "Rectangle, area = " << fixed << setprecision(2) << area() << endl;
}
};
// Triangle 類別
class Triangle : public Shape {
private:
double base, height;
public:
Triangle(double b, double h) : base(b), height(h) {}
double area() const override {
return 0.5 * base * height;
}
void info() const override {
cout << "Triangle, area = " << fixed << setprecision(2) << area() << endl;
}
};
// 印出圖形資訊的函式
void printShapeInfo(const Shape* shape) {
shape->info(); // 透過多型自動呼叫正確版本
}
/* 範例測試
int main() {
// 建立 Shape 指標陣列
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Rectangle>(5.0, 3.0));
shapes.push_back(make_unique<Triangle>(4.0, 6.0));
shapes.push_back(make_unique<Rectangle>(2.5, 7.0));
shapes.push_back(make_unique<Triangle>(3.3, 3.3));
// 逐一列印圖形資訊
for (const auto& shape : shapes) {
printShapeInfo(shape.get());
}
return 0;
}
*/
```
:::
---
### 挑戰 [LeetCode - Design Circular Deque](https://leetcode.com/problems/design-circular-deque)
<br/>

- 題目說明: 依題目說明設計「++`雙端佇列`++」類別
- 程式輸出: 根據 input 的操作,會呼叫成員函式
<br/>
🏅 參閱解答: [CPP Class Sol](https://leetcode.com/submissions/detail/1714469928/)
{"title":"CPP Lecture5","description":"CPP Lecture5.","image":"https://hackmd.io/_uploads/HkOXo0s1xl.png","contributors":"[{\"id\":\"08ecf684-cada-47c1-ad99-984ab62fb65e\",\"add\":29370,\"del\":3953,\"latestUpdatedAt\":1753704647891}]"}