# C++ 物件導向高級程式學習筆記 ###### tags: `Software Engineering` `Code` `C++` > 紀錄:Jimmy Shen > Reference video link: https://youtu.be/2S-tJaPKFdQ (主要參考資源: 高清 1080P C++面向对象高级编程(侯捷)) ## 1. C++程式簡介 * 編寫C++的兩種實作 * 基於對象(**Object Based**) * C++ class * class without pointer members: EX: Complex * class with pointer members: EX: String * 物件導向(**Object Oriented**) * Classes之間的關係 * 繼承(inheritance) * 複合(composition) * 委託(delegation) * 歷史簡介 * C++語言(1983發明) * 正式版: C++ 98 (v1.0) (1998) * C++ 03 (TR1, Technical Report 1) * C++ 11 (v2.0) (加入許多STD標準庫) * C++ 14 * C++歸納 * C++語言 * 基本語法 * C++標準庫 * 相關應用程式庫 ## 2. 標頭檔(header.h)與類(Class)的聲明 ![](https://i.imgur.com/0yrUgoE.png =400x) > C語言內有Data與Function來操作變數,但是會被外界看到的(Global值),可能會有複用的問題 > C++中,有class的概念,操作變數只會被class內部的Data Member以及 Member Function調用,具有保護機制 * C++ 程式碼基本形式 * **.h(header files for own)** * Classes Declaration (聲明) * **.cpp** * ex: main()檔案 * 會引用標頭檔,如:```#include <isostream.h>``` and ```#include "complex.h"``` * 延伸文件名稱(extension file name或稱副檔名)不一定是.h或.cpp,也可能是.hpp 或是 其他 或是 無延伸名,如何分別角括號與引號請見[這裡](https://docs.microsoft.com/zh-tw/cpp/preprocessor/hash-include-directive-c-cpp?view=msvc-170) * **.h(header files for STD)** * 標準庫 Standard Library * 在C++中,通常沒有延伸名,EX:```#include <iostream>``` * C++中,如果要引用C的函式庫的話,可以在原本C的標頭檔前,加上c並去掉.h即可,如:```#include <stdio.h>```變成```#include <cstdio>``` ### 標頭檔的防衛式聲明 - Header Guard :::success 所有標頭檔都應該使用 #define 防止標頭檔被多次引入 ::: 在標頭檔中,為了防止**重複編譯**的事情發生,專案中會使用防衛式聲明(又稱頭哨兵),來防護標頭檔被重複引用後,出現重複編譯的問題。寫法如下: ```cpp= #ifndef COMPLEX_ #define COMPLEX_ // The contexts we write // // #endif ``` 這邊參照Google Coding Style的寫法,建議的命名格式為 \<PROJECT\>\_\<PATH\>\_\<FILE\>\_H\_ ### Header標頭檔佈局 使用以下範例作為介紹,請**注意註解**的說明: ```cpp= #ifndef COMPLEX_ #define COMPLEX_ #include <cmath> class ostream; // 0. 前置聲明: forward declarations class complex; // Google Coding Style不建議使用前置聲明 complex& __doapl (complex* ths, const complex& r) //////////// /// class head class complex // 1. 類-聲明: class declarations { /// class body public: complex (double r = 0, double i = 0) : re (r), im (i) { } complex& operator += (const complex&); // 定義放在body之外做定義 double real () const { return re; } // 有些函數可以在此直接定義 double imag () const { return im; } // 這種定義方式叫做內聯(inline),優點是編譯速度快,但不能過於複雜 // 另外,回傳值前加入const,表示回傳值不可改變 private: double re, im; friend complex& __doapl (complex*, const complex&); }; //////////// complex::function ... // 2. 類-定義: class definition #endif ``` ### class template (模板)簡介 使用模板,可以減少程式量,而達到不同型別之適用。原理為,先命名一個T讓編譯器暫時保留型別,最後再讓使用者可以去定義這個型別。 ```cpp= template<typename T> class complex { public: complex (T r = 0; T i = 0) : re (r), im (i) { } complex& operator += (const complex&); T real () const { return re; } T imag () const { return im; } private: T re, im; ... } //////////// When we apply: complex<double> c1(2.5, 1.5); complex<int> c2(2, 6); ``` ## 3. 建構子函數 相關程式碼範例,請往上滑動到[標頭檔佈局](#Header標頭檔佈局) ### inline-內聯函數 ```cpp= double imag () const { return im; } // This is an inline function. // equal to: inline double imag(const complex& x) { return x.imag (); } ``` > Reference: https://dotblogs.com.tw/v6610688/2013/11/27/introduction_inline_function ### access level-訪問級別 :::success class 資料內部數據,盡量以private儲存,以達到封裝效果 ::: * ```public:``` 公開級別 * 外部檔案物件可以去訪問、提取這個區間的變數或函式 * 公開的,可以被內、外部看到使用的 * 可以被繼承 * ```private:``` 私用級別 * 外部檔案物件**無法**去訪問、提取這個區間的變數或函式 * 私用的,只能被class內部看見使用,達到封裝的效果 * 不能被繼承 * class內部有正在運作、處理的資料(數據),可以存成此級別 * ```protected:``` 被保護級別 * 外部檔案物件**無法**去訪問、提取這個區間的變數或函式 * 受保護的,只能被class內部看見使用 * **可以被繼承** ### constructor (ctor, 建構子函數) :::success 對於建構子函數的初始化,請善用**initialization**的寫法 ::: 以下為建構子函數範例: ```cpp= complex (double r = 0, double i = 0) // default argument (默認實參) : re (r), im (i) // initialization (初始列) { } ``` 使用初始列來初始化參數,可以加快程式編譯、運行流程,而非使用assignments的方式,如:```re = r; im = i;``` 對於使用建構子函數,使用以下方式可以宣告建構子函數: ```cpp= { complex c1(2, 1); complex c2; complex* p = new complex(4); ... } ``` ### overloading (多載) > Reference link: https://www.csie.ntu.edu.tw/~r95116/CA200/slide/C3_Overloading.pdf 在class內,可以使用overloading的方式撰寫函式,如以下範例: ```cpp= // 寫法一 double real () const { return re; } // 寫法二 void real (double r) { re = r; } ``` 以上這兩種寫法,代表不一樣的兩個函式,雖然名稱一樣,但經由不同的定義讓函式實現overloading。原因為,real函數編譯後的實際名稱可能為(取決於編譯器): ```objectivec= ?real@Complex@@OBENXZ ?real@Complex@@QAENABN@Z ``` --- ## 4. 參數傳遞與返回值 ### Singleton-constructor (ctor, 建構子函數)被放在private區 > Reference: https://shengyu7697.github.io/cpp-singleton-pattern/ > 應用時機:整份程式裡面,只容許**唯一一個class實例**進行時,可以應用。 有的建構子函數是可以放在private區域,他無法被外界呼叫。這種寫法叫做**Singleton**(單例) 範例程式: ```cpp= class A { public: static A& getInstance() { static A a; return a; } setup() { ... } private: // 注意,建構子位於private位置 A() {}; A(const A& rhs) {}; ... }; /// 呼叫這個class時,使用以下方式 A::getInstance().setup(); // 使用這個函數取得這個class ``` ### const member functions (常量成員函式) :::success class內部應該要加const就要加 (如果函式不會改動數據) ::: class在寫回傳值時,要注意是否使用const的問題。加上const於大括號"{}"前,代表回傳值為常量,不可改變。如以下範例: ```cpp= double real () const { return re; } //注意在"{"前面的 const double imag () const { return im; } ``` 這樣的程式碼寫法,是因應以下的函式呼叫方式: ```cpp const complex c1(2, 1); //創建一個const類型的class cout << c1.real(); cout << c1.imag(); ``` 使用者已經說明c1不能被改變值(加上const),但若在設計class時,回傳值沒有加上const,代表這數值可能會在real或imag被改變數值。因此與使用者的理念相違。 ### 參數傳遞: pass by value vs. pass by reference(to const) :::success 接收傳遞進來的參數,盡量以reference的方式傳遞(有例外須看狀況) ::: 1. **Pass by value** * 傳遞"整包"資訊,若傳遞資料量大,會比較慢 * 使用時機:被傳遞的變數,若**沒有被分配記憶體位址**,則使用此傳遞方式 * 範例: ```cpp= //建構子中,傳遞的double值 r 以及 i complex (double r = 0, double i = 0) // Here : re (r), im (i) { } ``` 2. **Pass by reference** * 傳遞這項變數的"指針(位址)",4個bytes,傳遞速度快,直接去引用該位置的資料 * 傳遞的這項變數,**有可能被改變** * 使用時機:被傳遞的變數,值有可能會改變,也有被分配記憶體位置時 * 範例: ```cpp= ostream& operator << (ostream& os, const complex& x){ // ostream傳遞位址,os這個變數將會被改變 return os << '(' << real (x) << ',' << imag (x) << ')'; } ``` 3. **Pass by reference to const** * 同2.的性質,傳遞位址 * 但傳遞的這項變數,**無法被改變** * 使用時機:被傳遞的變數,被傳遞的值不可能會被改變,又有被分配記憶體位置時 * 範例: ```cpp= complex& operator += (const complex&); // += 右邊的變數,是被加的,不會改變其量值,因此加上const ``` ### 返回值傳遞: return by value vs. return by reference(to const) :::success 返回值盡量以reference的方式來傳(有例外須看狀況) ::: 1. **return by value** * 回傳整包參數資料 * 使用時機:要傳的東西,會消亡(local變數),本來就++沒有其位址++。所以傳引用(reference)的話,會抓到亂碼,此時就不能傳引用 * 範例: ```cpp= double real () const { return re; } // 回傳值,因其沒有位址 ``` 2. **return by reference** * 回傳參數位址,速度快 * 使用時機:平常可以不用return by value的情況,盡量使用;或是最後回傳要進行assign value時可以使用 * 範例: ```cpp= complex& operator += ( const complex& ); // 回傳的值需要"賦值" 給變數 ``` ### friend (友元) 凡是加了friend關鍵字,即可自由取得private的成員。 ```cpp= // in class friend complex& __doapl (complex*, const complex& r); // in definition inline complex& __doapl (complex* ths, const complex& r) { ths->re += r.re; //自由取得friend的private成員 ths->im += r.im; return *ths; } ``` 另外,**相同class的各個objects互為freind**,如以下範例: ```cpp= class complex { public: complex (double r = 0, double i = 0) : re (r), im (i) { } ////// *** int func(const complex& param) { return param.re + param.im; } ////// private: double re, im; } ////// In testing { complex c1(2, 1); complex c2; c2.func(c1); // c2是可以呼叫func取用c1 private參數 } ``` ### class body 外的各種定義(definitions) => 跳過,直接在下一章節一並講解 --- ## 5. 操作符多載與臨時物件 > operator overloading 為全局函數(globel functions) ### operator overloading(操作符重載-1, 成員函數) this * 當重新撰寫操作符(operator)時,會遇到兩種狀況,此為第一種狀況,僅有兩個變數,其中第一參數會被改動,且需要被賦值;第二參數不動 * 這種操作符,通常會有一個**隱藏的**成員函數-this * 直接下範例,說明do assignment plus的實作: ```cpp= // do assignment plus (the STD function) inline complex& // ** 傳遞者不需要知道接收者是以reference形式接收 ** __doapl (complex* ths, const complex& r) { ths->re += r.re; //第一參數(ths)將會被改動 => 因此不加const詞綴,並且return by ref. ths->im += r.im; //第二參數(r)不會被改動 => 因此使用const& 詞綴 return *ths; // ** 傳遞者不需要知道接收者是以reference形式接收 ** } // we reference this function inline complex& complex::operator += (const complex& r) { return __doapl (this, r); // 注意隱藏關鍵字this } /////// When we implement: { complex c1 (2, 1); complex c2 (5); c2 += c1; // or c3 += c2 += c1; //為此,我們必須return by ref. } ``` ### operator overloading(操作符重載-2, 非成員函數) (無this) 與 temp object(臨時物件) typename() > reference: https://data-flair.training/blogs/operators-in-c-and-cpp/ * 為了對付client的三種可能用法,需要開發三個對應的函數 * 此外,下面這些函數**絕不可以**return by reference,因為返回的必定是個local object * typename() => 創建臨時物件 的方法 ```cpp= inline complex // pass by value,返回local object operator + (const complex& x, const complex& y) { return complex (real (x) + real (y), imag (x) + imag (y)); // complex() => 這就在創建一個臨時對象,其資料為暫存,function結束後就消失 } inline complex operator + (const complex& x, double y) { return complex (real (x) + y, imag (x)); } inline complex operator + (double x, const complex& y) { return complex (x + real (y), imag (y)); } /// // 此為return by reference的範例,會改變os這項值並做回傳 ostream& operator << (ostream& os, const complex& x){ return os << '(' << real (x) << ',' << imag (x) << ')'; } ``` --- ## Summary > reference: https://www.youtube.com/watch?v=IRfN6rg0jmg :::success 1. 所有標頭檔都應該使用 #define 防止標頭檔被多次引入 2. class 資料內部數據,盡量以private儲存,以達到封裝效果 3. 對於建構子函數的初始化,請善用initialization的寫法 4. class內部應該要加const就要加 (如果函式不會改動數據) 5. 接收傳遞進來的參數,盡量以reference的方式傳遞(有例外須看狀況) 6. 返回值盡量以reference的方式來傳(不回傳local objects的情況下) ::: --- ## 7. 三大函數:複製建構子,複製賦值,解構子 > 使用string作為範例 > Reference: https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three ### Big Three, 三個特殊函數 ```cpp= class String { public: String(const char* cstr = 0); // 建立建構子 String(const String& str); // 1. 複製建構子 String& operator = (const String& str); // 2. 複製賦值 ~String(); // 3. 解構子 char* get_c_str() const { return m_data; } private: char* m_data; // 設置指標,指向字串空間 } ``` 示意圖:![](https://i.imgur.com/Nh2dHeo.png) ### ctor 和 dtor (建構子與解構子) > new char初始值為'\\0' ```cpp= String::String(const char* cstr = 0) { if (cstr) { // 判別是否有值 m_data = new char[strlen(cstr)+1]; // 最後在+1 為結束符號'\0' strcpy(m_data, cstr); } else { // 未指定初始值 m_data = new char[1]; *m_data = '\0'; } } inline String::~String() { delete[] m_data; // 之前是new一個array,因此這邊要刪除array [] } ``` 另外,下方給出一個實例:說明如何創建這個類別 ```cpp= { String s1(), // 使用建構子創建 String s2("hello"); // 使用"複製建構子"方式創建 String* p = new String("world"); // 創建一個pointer指向String Class delete p; // 使用完後,記得要刪掉pointer內容 } ``` ### class with pointer members 必須有 copy ctor 和 copy op= :::warning 應當避免 淺複製(shallow copy)的發生,若無設定copy ctor 和 copy op=,編譯器將會使用預設的方式,會造成錯誤 ::: 以下是錯誤範例,應盡量避免 ![](https://i.imgur.com/Lobf7AD.png) ### copy ctor (複製建構子) > 以s1為藍本,創建對象 (深複製(deep copy)) ```cpp= inline String::String(const String& str) { // 直接取另一個object的private(同類之間互為friend) m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); } ////// in test { String s1("hello"); String s2(s1); //String s2 = s1; } ``` ### copy assignment operator (複製賦值函式) :::success 複製賦值要考慮到自我賦值的檢查 ::: ```cpp= inline String& String::operator = (const String& str) { // ** 檢測自我賦值(self assignment) ** if (this == &str) // 不僅僅效率問題,也有可能防止出錯 return *this; // 下一節講解 delete[] m_data; // 1. 先殺掉自己 m_data = new char[ strlen(str.len) + 1 ]; // 2. 重新分配位置 strcpy(m_data, str.m_data); // 3. 複製 return *this; // 傳值 } ``` :::info 另外補充一點: 1. 接在typename後面的& (EX: ```const String& str```),指的是"以reference的方式接收"的意思 (reference) 2. 在變數前面的& (EX: ```this == &str```),指的是"取該變數的位址"的意思 (address of) ::: ### 一定要在operator= 中檢查是否self assignment ![](https://i.imgur.com/IQTjm69.png) ### output函數 ```cpp= #include <iostream.h> ostream& operator << (ostream& os, const String& str) { os << str.get_c_str(); return os; } ////// for test { String s1("hello"); cout << s1; } ``` ## 8. 堆積(heap)、堆疊(stack)與記憶體管理 ### 所謂stack, heap * **Stack** * 是存在於某作用域(scope)的一塊內存空間(memory space)。例如當你使用函數,函數本身會形成一個stack用來放置它所接收的參數,以及回傳地址。 * 在函數本體(function body)內聲明的任何變量,其所使用的內存塊都取自上述的stack * **Heap** * 或所謂"system heap",是指由操作系統提供的一塊global內存空間,程序可動態分配(dynomic allocated)從中獲得若干區塊(blocks) * 動態取得,使用new, delete的方式做操作 ```cpp= class Complex { ... }; ... { Complex c1(1, 2); // c1 所占用的空間來自於stack Complex* p = new Complex(3); // Complex(3)是個臨時對象,其所占用的空間乃是以"new" // 自heap動態分配而得,並且讓 p 指著。 } ``` ### stack object的生命週期 ```cpp= class Complex { ... }; ... { Complex c1(1, 2); } ``` c1 便是所謂的"stack object",其生命在作用域(scope)結束之際跟著結束。 這種作用域內的object,又稱為auto object,因為它會被「自動」清除。 ### static local objects 的生命週期 ```cpp= class Complex { ... }; ... { static Complex c2(1, 2); } ``` c2便是所謂的static object,其生命在作用域(scope)結束之後仍然存在。 解構子不會被調用,直到程式結束。 ### global objects的生命週期 ```cpp= class Complex { ... }; ... Complex c3(1, 2); { ... } ``` c3便是所謂的global object,其生命在整個程式結束之後才會結束。可以把它視為一種static object,其作用域為「整個程式」 ### heap objects的生命週期 ```cpp= class Complex { ... }; ... { Complex* p = new Complex; // p 所指的便是heap object ... delete p; // 它的生命它被delete之際就結束了 } ``` 下面這個例子會造成記憶體流失(memory leak)的問題,因為作用域結束時,p所指的heap object仍然存在,但指針p的生命卻結束了,作用域之外也看不到p。 因此沒機會delete p ```cpp= class Complex { ... }; ... { Complex* p = new Complex; } // 作用域結束,造成記憶體流失 ``` :::success 有new一個值,就一定要搭配delete,否則會造成記憶體流失的問題 ::: ### new: 先分配memory, 再調用ctor ![](https://i.imgur.com/Vafo6OI.png) 2是一個轉型作用 在3中,因為建構子是成員函式,因此有隱藏字元this ### delete: 先調用dtor, 再釋放memory ![](https://i.imgur.com/dj4gQbx.png) ### 動態分配所得的記憶塊(memory block), in VC ![](https://i.imgur.com/fB7Ley4.png) 每一種class都分左右兩個,左邊為debug模式,右邊為release模式 紅色是cookie,紀錄儲存空間的大小,最後面的1指的是我這個空間被配置出去了(使用中) > 字節等同於位元組byte;一個pointer 有四個位元組;一個double 有八個位元組;兩個連續的16進位數字為一個位元組 > ![](https://i.imgur.com/XZUTZJC.png) ### array new 一定要搭配 array delete ![](https://i.imgur.com/W9LX6pQ.png) ## Summary :::success 1. 欲建立含有指標的class類型,記得要重新定義"Big 3"三個特殊函數 2. 應當避免 淺複製(shallow copy)的發生,若無設定copy ctor 和 copy op=,編譯器將會使用預設的方式,會造成錯誤 3. 複製賦值要考慮到自我賦值的檢查 4. 有new一個值,就一定要搭配delete,否則會造成記憶體流失的問題 5. array new 一定要搭配 array delete ::: ## 10. Appendix: 類模板、函式模板與其他 ### 靜態變數-static > reference: [C/C++ 中的 static, extern 的變數](https://medium.com/@alan81920/c-c-%E4%B8%AD%E7%9A%84-static-extern-%E7%9A%84%E8%AE%8A%E6%95%B8-9b42d000688f) > [C++ 變數的存放位置跟static三個用法](http://hatsukiakio.blogspot.com/2009/04/c-static.html) > ![](https://i.imgur.com/cAtTqbD.png) * 靜態變數 static variables (static 出現在 class內) * 靜態變數存放的位置是在.data中,並非區域變數所存放的stack中,與global variables類似 * 同一種class中的靜態變數為共用值,每個instance(每個建出來的相同類)都可以去使用這項變數 * 最經典的範例就是用來計數這個 class 總共生成了多少個 instance ```cpp= class Account { public: static double m_rate; // 靜態變數宣告 static void set_rate(const double& x) { m_rate = x; } }; double Account::m_rate = 8.0; // 可以在class宣告最後進行static變數的定義 // Implement - 調用static的兩種方法 int main() { Account::set_rate(5.0); // (1) 透過object調用 Account a; a.set_rate(7.0); // (2) 透過class name調用 } ``` ### Singleton (單例) > 參考之前講過的[程式碼](#Singleton-constructor-ctor-建構子函數被放在private區) * 單例Singleton 設計模式 * 這個class只希望產生一個對象 * 以下是單例較為平常的寫法,此寫法會有個問題-若外界無須調用這個class,會浪費已經被創建的a ```cpp= class A { public: static A& getInstance ( return a; ); // 對外的唯一窗口 setup() { ... } private: A(); // 將建構子放在private,因此外界無法透由創建的方式得到class A A(const A& rhs); static A a; // 只有一個 class A } // Implement A::getInstance().setup(); ``` 以下是較好的單例寫法: * Meyers Singleton ```cpp= class A { public: static A& getInstance(); setup() { ... } private: A(); A(const A& rhs); ... }; A& A::getInstance() // 當有人調用到他,a才會被創建,一旦創建就會存在 { static A a; return a; } // Inplement A::getInstance().setup(); ``` ### Function template 函式模板 > 回顧之前[類模板](#class-template-模板簡介)的形式 * 函式模板 * 創建模板函數,在製作class時加上```template <class T>```的宣告 * 編譯器會對function template進行引數推導(argument deduction): 抓到函式 -> 檢查是否符合模板 -> 連結相關運算子 -> show出結果 ![](https://i.imgur.com/SwiDhuo.png) ### namespace 命名空間 > reference: https://openhome.cc/Gossip/CppGossip/Namespace.html 以 std 作為例子示範: ```cpp= namespace std { ... } ``` 以下為三種命名空間的使用方式: #### using directive ```cpp= #include <iostream.h> using namespace std; // 直接宣告我下面的程式碼都使用這個命名空間 int main(){ cin << ...; cout << ...; return 0; } ``` #### using declaration ```cpp= #include <iostream.h> using std::cout; // 宣告特定函式使用該命名空間 int main(){ cin << ...; cout << ...; return 0; } ``` #### 每次使用函式每次都進行宣告 (Suggested) ```cpp= #include <iostream.h> int main(){ std::cin << ...; std::cout << ...; return 0; } ``` ## 11. 複合(Composition)與繼承(Inheritan) ### Composition(複合),表示has-a (class內有class a)- Adapter Pattern * Composition * 如圖,在class queue底下有一個class Sequence,這個Sequence又叫做deque * deque功能較為完整,而queue功能較為簡單 * class queue 內部包含著class deque,並使用其功能與變數 ![](https://i.imgur.com/GGlQdR2.png) ![](https://i.imgur.com/MgQDmSY.png) ![](https://i.imgur.com/mIzL6lS.png) ![](https://i.imgur.com/HFszKK0.png) ### Inheritan(繼承),Composition by reference-Bridge Pattern ![](https://i.imgur.com/A7SFsRf.png) * 他也擁有物件,但是為pointer/reference,使用指針相連 * pointer to implementation * 對於函數的繼承,子類繼承的是父類的調用權 ### Inheritan(繼承), 表示is-a ![](https://i.imgur.com/bFm3bgB.png) ![](https://i.imgur.com/mMCNmld.png) ## 12. 虛函數與多態 ### Inheritan(繼承) with virtual function(虛函數) > Reference: > https://medium.com/theskyisblue/c-%E4%B8%AD%E9%97%9C%E6%96%BC-virtual-%E7%9A%84%E5%85%A9%E4%B8%89%E4%BA%8B-1b4e2a2dc373 > https://shengyu7697.github.io/cpp-virtual/ > About Abstrat Class and Intervace: > https://www.cnblogs.com/oomusou/archive/2007/05/07/738311.html > https://stackoverflow.com/questions/12854778/abstract-class-vs-interface-in-c > https://www.tutorialspoint.com/cplusplus/cpp_interfaces.htm * Class內函數分類: * non-virtual function: * 你不希望derived class 重新定義(override,覆寫)它 * 不能夠被改變,只能沿用 * virtual function: * 你希望derived class重新定義(override,覆寫)它,且它是已經**有默認值**的。 * 可以被重新定義,也可以不用(這樣就會直接沿用default定義) * pure virtual function: * 你希望derived class一定要重新定義(override,覆寫)它,且它是**沒有被定義默認值**的。 ```cpp= class Shape{ public: virtual void draw() const = 0; // pure irtual function virtual void error(const std::string &msg); // (impure) virtual function int objectID() const; // non-virtual function ... } class Rectangle: public Shape{ ... }; class Ellipse: public Shape{ ... }; ``` ### Inheritan(繼承) with virtual 應用-Template Method * 如同windows作業系統,假設PowerPoint要進行開啟舊檔的動作 * 會經過幾個步驟: Check file name -> Search file -> Open file -> 在磁碟裡撈資料 * 程式碼可以表現如下: ![](https://i.imgur.com/bLk7Jz7.png) * 在此有幾個重點: 1. 看最左邊,CMyDoc繼承CDocument 2. CDocument中,有個叫做Serialize()的函數,並沒有定義(設為虛函數),就像只創造一個模板一樣,留到後續再進行創造與定義。這樣的技巧是一種稱為**Template Method**的設計模式 3. 最右邊看到執行主程式,會先到被繼承的類中運行呼叫的相關函數 4. 等到執行到需要被定義的虛函數時,會再跑到繼承的函數尋找這個虛函數的定義 5. 繼續被繼承的函數,繼續執行 * 實際程式碼範例: ![](https://i.imgur.com/ut7PMmK.png) ### Delegation(委託) + Inheritance(繼承) 應用-Observer Pattern * 應用情境:重複開許多視窗,這些視窗都會傾聽同樣的動作,顯示相同的或相異的畫面(但所提取的資料都相同) ![](https://i.imgur.com/Nqmwqxa.png) ![](https://i.imgur.com/FPs3Kmn.png) * 程式碼解釋: * 類似於Qt中,[Model View Architecture](https://dangerlover9403.pixnet.net/blog/post/215257848-%5Bqt%E6%95%99%E5%AD%B8%5D-qt%E8%B6%85%E7%B0%A1%E5%96%AE%E6%95%99%E5%AD%B8day-3-(-model-,-view-))的架構 * 這樣的技巧是一種設計模式-**Observer Pattern** * 使用者可以創建很多class去繼承Observer作為父類(一對多) * Class Subject物件可以使用容器裝載繼承Observer的子物件們,進行統一管理(做同步更新) * Class Observer 中,創建一個虛函數update進行資料更新。當物件(Subject)資料被進行修改時,會呼叫notify讓子物件們都同步做資料更新 ## 13. 委託相關設計 ### Delegation(委託) + Inheritance(繼承) 應用-Composite Pattern > Reference: https://www.gushiciku.cn/pl/pPh1/zh-tw > ![](https://i.imgur.com/hBJmkJW.png) ### Delegation(委託) + Inheritance(繼承) 應用-Prototype Pattern ![](https://i.imgur.com/OHYvbNR.png) > 名詞符號解釋: > ++下畫線++: static變數或函數 > 先寫obect名稱(:左邊),再寫type名稱(:右邊) > -名稱: 代表private變數或函數 > +名稱 or 名稱: 代表public變數或函數 > #名稱: protect變數或函數 > 名稱(): 代表建構子 > 名稱(int, ...): 代表函數 * Prototype Pattern設計模式 * abstraction(抽象)以上,為一種形式、版型、藍本。可以讓主要的框架看到,進行操作 * abstraction(抽象)以上,為子類,在未來才會被派生下去 * 上面會準備空間,下面新生的類,會再將他的原型放上去 * 程式範例 ![](https://i.imgur.com/N3h9knw.png) ![](https://i.imgur.com/HzgBcQr.png) ![](https://i.imgur.com/EU8NCos.png) ## 14. 導讀 https://youtu.be/satNNEwsaDU 比前一個課程,學的更深入。 ## 15. Conversion Function ### 轉換函數(conversion function)的使用方法 * 程式範例: ```cpp= class Fraction { public: Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { } operator double() const { return (double)(m_numerator / m_denominator); } private: int m_numerator; int m_denominator; } // Inference: Fraction f(3, 5); double d = 4 + f; //調用operator double() 將f轉換為0.6 // 編譯器會先找"+"的運算子,若沒有,轉換f看看能否編譯過。 ``` > 其中,operator那串function即為**conversion function** * 語法特點: * 轉換不能改變class內部的參數,因此通常需要加const * 不需要寫上return type(返回類型) ### 其他範例 ```cpp= // Class template <class Alloc> class vector<bool, Alloc> // 這個class裡面接為boolean值 { public: typedef __bit_reference reference; // 代理,reference應為boolean值 protected: reference operator[](size_type n){ return *(begin() + difference_type(n)); } ... } // In __bit_reference struct struct __bit_reference { unsigned int *p; unsigned int mask; ... public: //此為轉換函數 operator bool() const { return !(!(*p & mask)); } } ``` --- ## 16. Non-Explicit One Argument Constructor ### Argument vs. Parameter > Reference: https://shengyu7697.github.io/cpp-parameter-argument/ * Argument(引數) vs. Parameters(參數) * Parameter (參數) 是函式宣告 (或函式簽章) 裡的變數;Argument (引數) 是表示呼叫函式時所帶入的變數或數值。 * 影片中舉例:```Fraction(int num, int den=1)```的狀況下,我們可以說:Fraction函式中有兩個**參數**,但呼叫時只需要一個**引數**就夠了(因為另外一個已經有預設值)。 ### non explicit one argument constructor ```cpp= class Fraction { public: Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { } Fraction operator+ (const Fraction& f) { return Fraction(...); } private: int m_numerator; int m_denominator; } // Inference: Fraction f(3, 5); double d = f + 4; //調用non explicit ctor 將4轉換為Fraction // 接著再調用operator+ ``` * 特色:能夠將別種型別,透過non explicit的方式轉換為該種class的型別。(編譯器會自動調用協助轉換) * 但若與conversion function合併寫在一起時,編譯器會出錯(多於一條路線可行),會釋出[Error] ambiguous ### explicit one argument constructor > explicit: 明白、明確的意思 ```cpp= class Fraction { public: explicit Fraction(int num, int den=1) : m_numerator(num), m_denominator(den) { } Fraction operator+ (const Fraction& f) { return Fraction(...); } private: int m_numerator; int m_denominator; } // Inference: Fraction f(3, 5); double d = f + 4; //[Error] conversion form 'double' to 'Fraction' requested. ``` * 加上關鍵字explicit,說明編譯器不能自動幫忙轉換類別,需要使用者**明確定義** * 通常在**建構子之前** --- ## 17. Pointer-like Classes ### Pointer-like Classes, 智能指針 ```cpp= template <class T> class shared_ptr { public: T& operator*() const { return *px; } //將所指的內容回傳 T* operator->() const { return px; } private: T* px; long* pn; } // Foo 結構寫法 struct Foo { ... void method(void) { ... } }; // Inference shared_ptr<Foo> sp(new Foo); Foo f(*sp); sp->method(); //同px->method(); ``` * 重點在於:```*```以即```->```操作符的重載。沒有例外,都是這樣寫的。 * ```->```得到的元素,需要**再接著```->```作用上去** * ![](https://i.imgur.com/puqU6w6.png) ### Pointer-like Classes, 迭代器(iterator) * 迭代器作用也像智能指針一樣 * 但迭代器有許多運用,方便該「容器」可以做運算之用 * 舉```list```為例: ![](https://i.imgur.com/tcKxEua.png) ```cpp= T& reference operator*() const { return (*node).data; } T* pointer operator->() const { return &(operator*()); } // Inference list<Foo>::iterator ite; ... *ite; //取得一個Foo物件 ite->method(); //意義為調用Foo::method(); //相當於(*ite).method(); //也等於(&(*ite))->method(); ``` --- ## 18. Function-like Classes ### Function-like Classes, 仿函數(函數對象) ```cpp= template <class T> struct identity : public unary_function<T, T> { const T& operator()(const T& x) { return x; } }; template <class Pairs> struct select1st : XXX { const typename Pair::first_type& operator()(const Pair& x) const { return x.first; } }; ... // Inference template <class T1, class T2> struct pair { T1 first; T2 second; pair() : first(T1()), second(T2()) {} pair(const T1& a, const T2& b) : first(a), second(b) {} ... }; ``` * 重點在於改寫```()```操作符 * 標準庫中,仿函數都會繼承奇特的base classes --- ## 19. namespace 經驗談 https://www.youtube.com/watch?v=DgP-EbSpuSY ## 20. 類模板 https://www.youtube.com/watch?v=ALwnBsK_wAU ## 21. Funtion Template https://www.youtube.com/watch?v=2LWbRVKOFUo ## 22. Member Template https://www.youtube.com/watch?v=4-HRDl2hQ_A ## 23. specialization (特化模板) https://www.youtube.com/watch?v=5COEXgHb25k ## 24. partial specilization (偏特化) https://www.youtube.com/watch?v=VX-DfNwxqgA ## 25. 模板模板參數 https://www.youtube.com/watch?v=d6qrzDVn8T8 ## 26. 關於C++標準庫 https://www.youtube.com/watch?v=AF3WHSc8h7Q ## 27. C++11 三種主題 https://www.youtube.com/watch?v=4-1-wT4t2Hs ## 28. Reference https://www.youtube.com/watch?v=9AsMOrfRCkQ reference 當宣告完成時,就無法再重新代表其他東西 ## 29. 複合、繼承中的建構子與解構子 https://www.youtube.com/watch?v=vhOSeapZ03k ## 30. Vptr and Vtbl https://www.youtube.com/watch?v=MysC48nh9yI 動態綁定:1.透過指針;2.指針向上轉型;3.虛函數 => Compiler會進行虛機制(virtual machanica...),進行動態綁定。 ## 31. 關於this https://www.youtube.com/watch?v=WShbnGNkHlg ## 32. 關於動態綁定 https://www.youtube.com/watch?v=qwswGhYZE4Q (p) => 代表this pointer ## 33. 關於動態綁定 https://www.youtube.com/watch?v=WWbYbcIhtUI ## 34. 關於new delete https://www.youtube.com/watch?v=I5KahB0XRY4 ## 35. Operator new,operator delete https://www.youtube.com/watch?v=yTxuNxYVDaY ## 36. https://www.youtube.com/watch?v=B2z6YWL6xMs ## 37. 重载new,delete$示例 https://www.youtube.com/watch?v=EuIrt0dBZco https://tw-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/comments.html https://m-peko.github.io/craft-cpp/posts/different-ways-to-define-binary-flags/