#分享 [筆記]類別、特殊函式、內嵌函式、函式物件|C++

2021年7月8日 13:18
程序導向的C語言也能實作物件導向?C語言要如何模擬類別?C struct與C++ struct與C++ class這三者有什麼差別?類別中有哪六個特殊函式?初始化和賦值有什麼差別?內嵌函式和巨集很像?物件居然可以拿來當成函式?為什麼C++宣告無參數物件時,不用加()? 這是一篇中山資工所 江明朝老師 物件導向程式設計課程筆記,如內容有錯,歡迎留言交流~ 還是很困惑嗎?文章裡有答案喔~😎 好讀版:
一、類別 (一)物件導向 為什麼會從程序導向走向物件導向?因為現實生活中,很多都是以物件的方式呈現。物件導向主要三個功能: 1. 封裝 (encapsulation):改善C語言的封裝與權限問題,封裝完整的話,有利於debug,讓錯誤限制在某一個區塊。 2. 繼承 (inheritance):重複使用程式碼。 3. 多型 (polymorphism):可以解決switch的問題。同樣函式名與參數,即同樣的函式簽章 (signature),能給出不同的定義。不用動別人的原始碼就可以改變別人函式的功能。 (二)C++ 類別 (class)與結構 (struct) C++的struct和class兩者幾乎沒有差別,但實務上會希望有所區分。 C++ 的 struct 和 class都可以: 1. 宣告成員資料 2. 宣告成員函式 3. 繼承、多型、建構子、解構子、interface C++物件把data和operations分開,class = data type (data member) + operation (member function)。 更多存取權限與繼承模式,可以參考[筆記]繼承模式與存取權限|C++ (三)C語言模擬類別 (class) 但C struct和C++ struct就不太一樣,C struct可以(1)宣告成員變數,而(2)成員函式和(3)物件導向的東西都不能使用。不過C仍可使用struct模擬class,C struct要用指向函式的指標來代替成員函式,所以C struct的實例 (instances)中,除了有成員資料,還會有很多的指標變數來儲存韓式;而C++ class使用this指標去指向成員函式,以達到每個實例共用同一套成員函式的目的,因此每個class實例只會存放成員資料,故C++ class實例的大小就會比C struct實例來得小。 使用C struct模擬C++ class,struct client_t有四個變數,三個是資料,第四個是指向函式的指標 程式碼來源 如果想要C struct實例共享同一套函式,則可以直接使用extern,使其他檔案都可以這一套函式,對C struct實例做操作。 物件 (object)、實例 (instance)、變數 (memory allocation),三者概念相近。Struct使用dot (.)存取資料,Class也是使用dot (.),但如是指標,則要使用箭號(->)或start (*) + dot (.)。 二、特殊成員函式 類別有6個特殊成員函式 (special member function),C++會自動幫你產生,通常一個要更動,其他特殊函式可能也要更動。 預設建構子 (default constructor) 解構子 (destructor) 複製建構子 (copy constructor) 複製運算子 (copy assignment operator) 移動建構子 (move constructor) 移動運算子 (move assignment operator) (一)建構子 (constructor) C語言中常會忘記初始化就直接使用,這樣會出現一些神奇的問題,所以C++有預設的建構子,就可以避免這樣的問題。C++建構子會幫你自動初始化變數,避免用到未初始化的變數。 class::constructor (int a, int b) :data1(a), data2(b) // Initialization,使用初始列較有效率 { data1 = a; data2 = b; } // Assignment,較沒效率 1. 初始化 (Initialization) 通常會和宣告(配置記憶體)綁在一起,然後賦值給未初始化的變數,故需要配置+賦值,如:int a =10; 2. 賦值 (Assignment) 用另一個值給已經初始化的變數,所以要先clean up已初始化的變數,故只更新值,如:a = b; 3. 靜態變數 (Static variable) 傳遞參數是初始化還是賦值呢?是初始化,因為傳遞參數時,我們可以使用常數變數,而常數變數只能初始化一次,且不能做賦值 (assignment),故是初始化的動作。如: 傳遞參數:int func(const int a); const int c = 1; //合法 const int d; d = 2 // 非法,因為d已經被初始化為0,不能再賦值為2 (二)其他特殊函式 template<class T> class Handle { T* p; public: Handle(T* pp) : p{pp} {} // default constructor ~Handle() { delete p; } // destructor Handle(Handle&& h) :p{h.p} { h.p=nullptr; } // move constructor, 舊的object一定要接上nullptr,因為如果不接上nullptr,新舊參考都指向同一個東西,舊的object會自動呼叫destructor,則舊的object所指向的東西也會一並刪除,此時新的參考所指向的東西就會被free掉。 Handle& operator=(Handle&& h) { delete p; p=h.p; h.p=nullptr; return *this; } // move assignment operator, 少了if(this != &h) Handle(const Handle&) = delete; // copy constructor, delete 是指no copy Handle& operator=(const Handle&) = delete; // copy assignment operator, delete 是指no copy // ... }; 原始碼連結 三、內嵌函式 (Inline function) 內嵌函式和巨集 (macro)很像,都是文字取代機制,可以降低函式指標跳轉的時間。因此內嵌函式可以取代巨集的函式定義的功能,且內嵌函式可以避免巨集的一些問題,尤其是++或--的時候,不過缺點就是記憶體用量變多。 內嵌函式: inline int max (init a, int b){ return a>b ? a : b; } int m = max(++x, ++y); // 這邊x或y只會被加一次 巨集: #define max(a, b) a>b ? a : b int m = ++x > ++y ? ++x : ++y; // 這邊x或y會被加二次 如果要宣告內嵌函式,要在header中宣告+實作,不能將實作分開在cpp中。因為編譯器要知道內嵌函式的實作內容,才能做文字取代,不然編譯器不知道實作內容,就無法插入完整的程式碼。 四、函式物件 (functional object) 函式物件是將物件當成函式來使用,搭配多載運算子"()"一起使用。 struct FO{ int operator()() { return 100;} }; C++ struct幾乎和C++ class一樣,多載運算子() Fo fo; std: :cout << fo() << std::endl; Call fo.operator()() 呼叫fo物件的多載運算子() std::cout << FO()() << std::endl; Call default constructor == FO().operator()() 呼叫預設建構子的多載運算子() FO()是在呼叫default constructor,第二個()是在呼叫多載運算子() std: :cout << FO{}() << std: :endl; Call default constructor, {} is initializer, == FO{}.operator()() 一樣是呼叫預設建構子的多載運算子(),只不過這裡是使用初始化列{}來初始化物件。 C++11的lambda表達式,由於編譯器會將lambda表達式轉成函式物件,故也是一種函式物件,只不過它是一種匿名的函式。 為什麼C++在宣告沒有引數的物件時,不能加()?因為C++會無法分辨className()為建構子還是函式物件這算是C++先天的缺陷。因此C++認為className()為函式物件,而className為建構子。 五、相關文章 1. 物件導向程式設計|模擬真實世界的方式|江明朝
江老師物件導向程設,評分、老師、課程簡介。 2. AOOP Homework source code
3. [筆記]109-2高等物件導向程式設計 期中考
4. [筆記]介面與實作、運算子多載、左值右值、參數傳遞、回傳多值|C++
運算子多載如何執行?int a = 10; ++a—;這串程式碼為什麼會出錯?居然跟左值又值有關!左值右值真的是一左一右嗎?參數傳遞中的傳指標和傳參考差在哪邊?C++居然無法回傳多值,那我該怎麼回傳多值? 5. [筆記]陣列與指標|C++
我以為我傳的是陣列,但居然傳的是指標?為什麼我無法回傳陣列?只能回傳指標?所以陣列和指標是什麼關係?指標運算怎麼算?釋放動態記憶體時,指標變數會不會也被釋放? 6. [筆記]類別、特殊函式、內嵌函式、函式物件|C++
程序導向的C語言也能實作物件導向?C語言要如何模擬類別?C struct與C++ struct與C++ class這三者有什麼差別?類別中有哪六個特殊函式?初始化和賦值有什麼差別?內嵌函式和巨集很像?物件居然可以拿來當成函式?為什麼C++宣告無參數物件時,不用加()? 7. [筆記]繼承模式與存取權限|C++
繼承模式和存取權限有什麼關係?private繼承不是讓所有存取權限變成private嗎?為什麼衍生類別依舊可以存取基礎類別?類別開發者和使用者有什麼區分?特殊函式的繼承會和一般函式一樣嗎? 8. [筆記]多型與繼承的關係|C++
多型的出現是為了要解決什麼問題?多型觸發的條件是什麼?為什麼多型要和繼承一起使用?多型能保有繼承的優點嗎?多型比繼承又多了什麼功能?特殊函式也能多型嗎? 9. [筆記]static / const成員資料與函式|C++
non-const static成員資料無法在類別內初始化,也無法使用初始化列,那到底該如何初始化?static成員是什麼概念?可以將自己宣告為自己的成員資料嗎?如果可以,該如何實現?如果不行,會發生什麼問題? 10. [筆記]夥伴函式與類別、不夠朋友問題|C++
夥伴函式和成員函式差在哪裡?為什麼輸入/輸出的多載運算子一定要為夥伴函式?如果A類別把B函式當作夥伴,但B函式的參數中沒有A類別,顯然這個B不把A當夥伴,就會造成main()無法找到B函式。
14
留言 1
文章資訊
85 篇文章300 人追蹤
Logo
每天有 5 則貼文
共 1 則留言
臺北醫學大學
謝謝你,解答了我好多疑問