# CPP Lecture5 ![upload_0673f86c099c4a1a5741d9a110bedecf](https://hackmd.io/_uploads/rJaDXxjo1x.png) - __學習基礎++物件導向程式設計++概念 (`OOP`)__ - __認識結構體 (`struct`) 以及如何創建__ - __認識類別 (`class`) 以及如何創建__ - __兩者區別在哪?`OOP` 的邏輯架構?__ --- ## Object-Oriented Programming ![螢幕擷取畫面 2025-07-20 180439](https://hackmd.io/_uploads/ByJ2Jr5Lxx.png) :::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++ 來索取私人成員 > &nbsp;&nbsp;&nbsp;&nbsp; 最後進行屬性隱私檢查,確保外部無法直接訪問私人的成員變數 --- ### ✏️ 練習 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/> ![image](https://hackmd.io/_uploads/rJ1n_yHDll.png) - 題目說明: 依題目說明設計「++`雙端佇列`++」類別 - 程式輸出: 根據 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}]"}
    145 views