# C++ Class ## 前言 類別(Class)由成員(member)組成,包括資料、函數以及建構函數。 ## 所以...甚麼是Class? 我們可以把Class當成是一個學生,而這個學生有姓名、身高、體重、年齡,有個函數可以來計算它的BMI,也要有一個函數可以「創建」這個學生。以下為範例: ```cpp= class student { // member variables string name; double height, weight; int age; // member function double BMI(); // constructor student(); student(string name, double h, double w, int age); }; ``` ## 建構函數(Constructor) 建構函數有兩種寫法,一是直接用函數寫,另外則是利用 `:` 來初始化成員變數 ```cpp= student::student(string _name, double _h, double _w, int _age) { this->name = _name; this->height = _h; this->weight = _w; this->age = _age; cout << "Construct successfully!"; // 不須 return } ``` 初始化列表(各變數以`,`分隔): ```cpp= student::student(string _name, double _h, double _w, int _age) : name(_name), height(_h), weight(_w), age(_age) { cout << "Construct successfully!"; } ``` 通常初始化列表較常用在 const 成員上,因為 const 成員不可賦值 ## 解構函數 通常用在需要釋放記憶體之成員函數:`student::~student() {}` ## 成員函數 定義成員函數: ```cpp= student::BMI() { return this->weight / pow(this->height, 2); } ``` 呼叫成員函數: ```cpp= student s1("John", 1.8, 80, 18); cout << s1.BMI(); ``` *** ## 存取標籤比較 ![](https://github.com/ChocomintSSR/Chocomint_Personal_Page/blob/main/images/class1.png?raw=true) ## 實作 - Vector2D 在 Vector2D 中,有兩個 double 的值,分別代表座標的 x, y,以及基本的初始化函數: ```cpp= class Vector2D { private: double x, y; public: Vector2D(double X, double Y) : x(X), y(Y) {} }; ``` 通常,我們會將 class 內的變數設為 private,以避免外部經任意手段更改。 因為變數不可存取,我們需要另外寫一個函數來取得他們的值: ```cpp= public: double getX() const { return this->x; } double getY() const { return this->y; } ``` ### this 延續上面的例子,事實上,`this` 是一個**指標型態** ```cpp= Vector2D *this; // this 的原型 ``` 因為 `this` 是指標型態,並且 `.` 的優先順序在 `*` 之前,因此我們需要用 `(*this).getX()` 來呼叫函數,但這樣顯得過於麻煩,因此C++提供了一個好方法:**`this->getX()`** ### const 我想有人看到 `const` 可能會不懂它是什麼東西 `const` 是一個關鍵字,它的用途就是讓指定物件或變數不可修改 講白就是被 `const` 修飾 (decorate)過的不能被賦值 (assign) ```cpp= int value = 5; const int const_value = 5; value = 10; // 正確,因為value是個變數 const_value = 10; // 錯誤,因為const_value是個const int ``` 那如果把函數傳入的參數加上個`const`會變成怎樣呢? ```cpp= void test_function(const int a) { a = 5; // 錯誤,因為a是const int a++; // 錯誤,同上 std::cout << a << '\n'; //正確 } ``` 那如果再把參數改成傳引用(reference)呢 ```cpp= void test_function(const int& a) { a = 5; // 錯誤,因為a是const int& } ``` 這樣有什麼好處呢? 因為用 `const` 修飾過,所以參數會受到保護,免得被亂改,傳入引用會讓效率變高且減少空間需求(在物件所佔的空間較大會比較明顯 再之後的get函數中會在函數後面加個 `const`,那這又是什麼呢? ```cpp= getX() const { return this->x; } ``` 這個 `const` 表示getX()不會修改成員變數 如果要函數傳進去的物件是有 `const` 修飾過的,那這個物件只能調用 `const` 修飾過的函數 (誰聽得懂,直接上code) ```cpp= double Vector2D::getX_const() const { return this-> x; } double Vector2D::getX() { return this-> x;} void test(const Vector2D other) { other.getX_const(); // 正確,因為getX_const()是個const的成員函數 other.getX(); // 錯誤,因為const物件不能調用非const的成員函數 } ``` 講了這麼多 `const` 的用途,看起來挺麻煩的 但適當的加 `const` 可以讓程式的穩定性更加,速度也會提升 (也可以提醒自己,這個東西是不能亂改的啊),請各位謹記 ### & - 引用 (reference) 我想各位在 `const` 篇應該有看到 `&` 這個符號吧,想必又是看不懂了 (這作者到底怎樣,都喜歡先斬後奏的) 「引用」就是一個變數的別名 (alias),它一定會指向一個變數,且引用是跟變數共用相同記憶體區域的,當讀取這個別名時,就可以得到它所指向的變數 (variable) 的值 (value) 了 呃,有聽沒有懂,直接上code🤣 ```cpp= int value = 5; //宣告變數 (declare variable) int &reference_value = value; //宣告引用並指向value (declare reference) value = 6; std::cout << value << '\n'; //輸出6 reference_value = 7; std::cout << value << '\n'; //輸出7 //證明reference跟variable的記憶體區域是相同的 //可以自己試試,在這裡的&是取地址(address) std::cout << &value << '\n'; std::cout << &reference_value << '\n'; //以下為錯誤示範 int &reference_value_1; //錯誤,因為引用必須要指向一個變數 int &reference_value_2 = 5; //錯誤,因為5並不是個變數 ``` 由上述code可得知,引用必須要指向一個變數,且修改引用的值就等於修改引用的變數的值,這有什麼好處呢? 因為需要指向變數,所以就不存在初始化 (initialization)的問題,不用擔心引用裡面會存什麼自己不知道的東西 另外,因為引用是共用記憶體區域的,所以在傳參數時傳入引用是可以減少記憶體開銷的 ```cpp= void swap1(int a, int b) { int temp = a; a = b; b = temp; } void swap2(int &a, int &b) { int temp = a; a = b; b = temp; } int main() { int a{10}, b{20}; swap1(a, b); std::cout << a << " " << b << '\n'; //仍然為10跟20 swap2(a, b) std::cout << a << " " << b << '\n'; //變成了20跟10 return 0; } ``` 上述就是使用引用來達成交換函數的功能,上面也顯示了 一般傳入參數會是複製一份新的給函數使用,而傳引用則是直接使用外部的參數使用,這對記憶體開銷會比較小一點 那問題來了,因為會直接修改到外面的參數,那要怎麼避免呢?請回去看 `cosnt` 篇 🤣 ```cpp= void test(const int& a) { a = 5; //錯誤,因為a是個const int reference,不可被修改 } ``` 有關`&`的注意事項: 函數返回值時不可以返回區域引用,會出事的,至於原因各位可以想想 ## Operator Overloading 接下來,我要隆重介紹一個非常重要的一種函數——**運算子(operator)** 運算子是一個符號,也是一種函數,它可以告訴編譯器執行指定的數學關係或邏輯運算並產生結果。 在C++中,部分運算子是可以被重載(overloading)的 關於可重載的列表見維基百科 [運算子優先級與可重載的運算子列表](https://zh.wikipedia.org/wiki/C%E5%92%8CC%2B%2B%E9%81%8B%E7%AE%97%E5%AD%90#%E9%81%8B%E7%AE%97%E5%AD%90%E5%84%AA%E5%85%88%E7%B4%9A) ### 基本構型 ```cpp= [return_type] [class]::operator[op]((const) [data_type] (&)[data_name], ...) ``` 直接舉個例子吧。延續上面的 `Vector2D`,現在我們想要將兩個向量相加,那麼我們應該這樣寫: ```cpp= const Vector2D operator+(const Vector2D &other); ``` 這樣的意思是要把 **`*this`** 跟 **`other`** 相加,因此,若我們將其使用後會是這樣: ```cpp= Vector2D a, b; a.operator+(b); a + b; // 同第2行 ``` ### 定義函數 有兩種寫法: 1. 直接寫在 class 中 ```cpp= public: const Vector2D operator+(const Vector2D &other) { double x0 = this->x + other.x; double y0 = this->y + other.y; return Vector2D(x0, y0); } ``` 2. 寫在 class 外 ```cpp= ... public: const Vector2D operator+(const Vector2D &other); // 先宣告於class內 } const Vector2D Vector2D::operator+(const Vector2D &other) { double x0 = this->x + other.x; double y0 = this->y + other.y; return Vector2D(x0, y0); } ``` *特別注意:如果是在成員函數內,可直接取用 class 內的 private 成員* 那如果我想寫 `int + Vector2D` 呢? 事實上,operator也可以當作一般函數定義: ```cpp= const Vector2D operator+(const int &n, const Vector2D &other) { double x0 = n + other.x; double y0 = n + other.y; return Vector2D(x0, y0); } ``` 特別注意,這個函數不須寫在 class 中。 ### 單元運算子 單元運算子分成兩種,一是加在變數前面,另一則是加在後面 加在前面的像是:負號 `-x`、邏輯反 `!x`;加在後面的則像是:後綴遞增 `x++` 1. 負號的重載 ```cpp= const Vector2D operator-(Vector2D &other) {...} ``` 2. 後綴遞增的重載 ```cpp= Vector2D Vector2D::operator++(int) {...} ``` 特別注意 **`int`** 一定要加 ### 做個小練習 現在請寫一個 class 叫做 complex,裡面包括: 1. **private** 的實部 real 以及虛部 imag 2. 建構子(傳入 double r, i) 3. 可取得實部及虛部之函數 Re(), Im() 4. 加減乘除運算(complex +-\*/ complex 即可) ## 繼承 (inherit) 舉另一個例子吧,假設我有一個 class 要來存儲一個交通工具,但我同時有汽車、公車、飛機,如果分開寫,那就太麻煩了。因此,我們先寫好共同的項目,再用「繼承」寫個別的東西。 ```cpp= class Vehicle { public: int fuel, capacity; // constructor Vehicle(const int& f, const int& c) : fuel(f), capacity(c) {} double CP() { return fuel / capacity; }; }; class Car : Vehicle // 繼承 { public: Car(const int& f, const int& c) : Vehicle(f, c) {} // 建構子繼承 }; ``` 如此一來,我們不需要把所有交通具都寫出來,而只需要寫一個大綱,在繼承出去寫細項即可。 ```cpp= Car toyota(50, 5); cout << toyota.fuel << "\n" // 50 << toyota.capacity << "\n" // 5 << toyota.CP() << "\n"; // 10 ``` *** ###### tags: `tutorials`