# 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();
```
***
## 存取標籤比較

## 實作 - 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`