--- 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 ! 還是會繼承到 **基礎類別的私有部分,會變為衍生類的一部份,但是只能透過基礎類的公用 & 保護成員函數儲存**,父類私有還是私有的,但是能可透過公開成員方法訪問父類成員變數,下圖可以看出 > ![](https://i.imgur.com/j0ulLF1.png) ::: * 嘗試使用衍生類 (子類) 直接操作基類 (父類),編譯時就會報錯,**私有成員一定要透過 public 成員函數訪問 (protected 除外)** > 同 Java Field 領域範疇 > > ![](https://i.imgur.com/EqROq4P.png) * 除了私有成員外,**子類無法呼叫父類的私有方法 (不可以訪問)** > 同 Java Method 領域範疇 > > ![](https://i.imgur.com/5AVWB43.png) ### 建構函數 & 析構函數 * **衍生類建構時會 ^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; } ``` > 可看出它的建購 & 析構順序 > > ![](https://i.imgur.com/w3lbz0W.png) ### 衍生 & 基礎類 關係 * 重要關係,**==基礎類別 reference 可以參考衍生類別物件==**,但**衍生類 reference 不可以參考基礎類別**,**++子類有的東西父類不一定有++** > reference 可以有 ==ptr *== or ==ref &== > > ![](https://i.imgur.com/37GbBmX.png) 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; } ``` **--實作--** > ![reference link](https://i.imgur.com/vxnj39B.png) 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; } ``` **--實作--** > ![](https://i.imgur.com/nmXQxBr.png) ### 向上、向下轉型 * **將衍生類別的 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 繼承 ::: > ![](https://i.imgur.com/889dY1R.png) ### 依賴 has-a * 繼承關係不能表示出 has-a 的關係;**最簡單的方法就是讓內部有 has-a 的成員**,讓這個類產生 [**聚合**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA#關係)關係,就像是午餐內有香蕉 :::info UML 中的 [**聚合**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA#關係) 關係 ( 1 ~ 多個對象) ::: > ![](https://i.imgur.com/FyzvkKz.png) ### 比喻 is-like-a * 繼承關係也不能描述比喻關係,就像是`抱枕` & `女友`,**只能取相同別有的特性,設計一個共享類別**,不能說抱枕就是女友,可用 ABC 來設計 (基礎抽象類別) :::info UML 中代表有共同抽象類 (abstract) 的衍生類 ::: > ![](https://i.imgur.com/n9iIHrs.png) ### 實作 is-implemented-as-a * 繼承關係也不能描述 `implemented` 關係,**可用 array 製作 stack,但 array != stack**,兩者個關係是都可以儲存,並沒有繼承這麼強烈的關係 :::info UML 中代表有共同接口類 (interface) 的衍生類 ::: > ![](https://i.imgur.com/X9PM3ZK.png) ### 依賴 uses-a * [**依賴關係**](https://hackmd.io/QEubF6EIRkGwHVa48Bo2bA?view#關係),跟組合有點像,但是組合關係會更重一點,但筷子 & 午餐並無關係 (筷子可以拿來插丸子之類的? 不一定要吃午餐),相對關係只是依賴 :::info UML 中代表類的 **依賴關係** ::: > ![](https://i.imgur.com/DvnNBv4.png) ## 同名異式的公用繼承 > 以 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; } ``` **--實做--** > ![](https://i.imgur.com/rcrJNiE.png) ### 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 的實現) > ![](https://i.imgur.com/3AIHUWg.png) * **無 Virtual 描述**:使用 ptr、ref 找到 **++沒有使用++ virtual function 後會直接根據 ++指標型態++ 決定 function 的實作** (當前指標型態為 Fruit,所以找 Fruit 的實作) > ![](https://i.imgur.com/LMCsFVF.png) ### 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 的提示改掉 > > ![](https://i.imgur.com/PePJpwX.png) ## 靜態 & 動態連結 :::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 時呼叫成基類** > ![](https://i.imgur.com/YRXPiQR.png) ### 虛擬函數的運作 > 知道虛擬 (Virtual) 函數的運作方式更有助於了解虛擬函數的概念 * 編譯程式處理虛擬函數的一般方法是使用,**在每個 class 物件中加入一個隱藏成員,該成員儲存一個指標,++指向函數位址的陣列++** > 該陣列稱為 ==**虛擬函數表格**== **++virtual function table++、++vtbl++,這表格內儲存虛擬數在宣告時的位子** * 基礎類別物件也會有一個指標,指向儲存該類別所有虛擬函數之位址的表格,**如果衍生類別重新定義虛擬函數,則表格儲存新函數的位址** > ![](https://i.imgur.com/a4rBb2d.png) ```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(); }; ``` > ![](https://i.imgur.com/g0iGZig.png) ### 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 就代表該類別為抽象類** > ![](https://i.imgur.com/uR3uVIn.png) ::: * 被宣告出來的純虛擬函數一定要定義,如果沒定義純虛函數編譯時就會出錯 > ![](https://i.imgur.com/s3V9dpj.png) ### 抽象函數 :::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://i.imgur.com/dctEtbD.png) ### 繼承 & 動態記憶體 * 使用繼承 + 動態記憶體時,同之前提到的 [**動態記憶體**](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; } ``` > ![](https://i.imgur.com/4nurVN8.png) ## Appendix & FAQ :::info C++ 真ㄊㄇ的繁瑣 ::: ###### tags: `C++` `設計`