---
# System prepended metadata

title: Class
tags: [' note', books]

---

---
tags: books, note
---

# Class

如果今天在直角座標平面上有幾個點需要表示

![](https://i.imgur.com/FJfy1er.png)

學`class`之前可能會馬上想到用兩個陣列

e.g.

```cpp=
#include<iostream>
#include<iomanip>
using namespace std;
int main() {
	//{ 2,  5},{-5,  3},{ 1, -1},{-4, -1},{ 5, 1}
	int x[5] = {2, -5, 1, -4, 5};
	int y[5] = {5, 3, -1, -1, 1};
	for (int i = 0; i != 5; i++)
		cout << "(" << setw(2) << x[i] << ", " << setw(2) << y[i] << ")" << endl;
	return 0;
}
```

```
result:
( 2,  5)
(-5,  3)
( 1, -1)
(-4, -1)
( 5,  1)
```

這樣寫固然輕鬆，但如果操作複雜一點呢?
假設需要對`x`進行排序

i.e.

```cpp=
#include<iostream>
#include<iomanip>
using namespace std;
void sort(int key[], int val[], int size) {
	for (int i = 0; i < size; i++)
		for (int j = i; j < size; j++)
			if (key[j] < key[i])
				swap(key[i], key[j]), swap(val[i], val[j]);
}
int main() {
	//{ 2,  5},{-5,  3},{ 1, -1},{-4, -1},{ 5, 1}
	int x[5] = {2, -5, 1, -4, 5};
	int y[5] = {5, 3, -1, -1, 1};
	sort(x, y, 5);
	for (int i = 0; i != 5; i++)
		cout << "(" << setw(2) << x[i] << ", " << setw(2) << y[i] << ")" << endl;
	return 0;
}
```
```
result:
(-5,  3)
(-4, -1)
( 1, -1)
( 2,  5)
( 5,  1)
```
因為內建`sort`沒有辦法在判斷其中一個值的同時改變與其下標相同的另一個值
所以必須要自己寫一個`sort`
現在是因為我刻的`sort`簡單，看起來才會沒這麼複雜
但如果對於`sort`需要到$O(nlogn)$的速度的話，自己寫勢必就沒效率又容易錯

而這個問題的其中一個解決方法就是使用**class**

---

## Declaration

我們可以把`class`想成是自己定義的型態

在這個問題中，我們需要的是一個能分別儲存`x`和`y`值的型態
先姑且叫做`position`

那在C++我們可以這樣宣告

```cpp=
class position{

};
```

與函數不同
記得在大括號的最後打上分號

---

## Member variable

有了`position`這個`class`
接下來我們希望能在每個`position`都擁有`int`型態的`x`跟`y`

i.e.
```cpp=
class position {
    int x;
    int y;
};
```
`int`型態的`x`和`y`就是position這個`class`的成員變數

這一段的意思是
1. 定義一個`class`叫`position`
2. 每一個`position`包含兩個`int`變數`x`跟`y`

那要如何存取呢
```cpp=
int main(){
    position pos;
    pos.x = 5, pos.y = 10;
    cout << pos.y << endl;
}
```
像這樣使用 `.`+`(變數名稱)`就可以存取那個變數

一開始提到的直角座標就可以改成像這樣的形式

i.e.

```cpp=
#include<iostream>
#include<iomanip>
using namespace std;
class position {
	int x;
	int y;
};
int main() {
	//{ 2,  5},{-5,  3},{ 1, -1},{-4, -1},{ 5, 1}
	position pos[5];
	pos[0].x =  2, pos[0].y =  5;
	pos[1].x = -5, pos[1].y =  3;
	pos[2].x =  1, pos[2].y = -1;
	pos[3].x = -4, pos[3].y = -1;
	pos[4].x =  5, pos[4].y =  1;
	for (int i = 0; i != 5; i++)
		cout << "(" << setw(2) << pos[i].x << ", " << setw(2) << pos[i].y << ")" << endl;
	return 0;
}
```

:::danger
error: could not convert '{2, 5}' from '\<brace-enclosed initializer list\>' to 'position'
error: 'int position::x' is private within this context
:::

改完之後會出現這樣子的錯誤訊息
那這是正常的

因為我們還沒加上十分重要的一行 ⸺ **public:**

---

## Private/Public


```cpp=
class position {
public:
    int x;
    int y;
};
```

`public:`的作用是讓`public`以下的內容在`class`以外也能使用

原本上面那個`error`寫說`'int position::x' is private`是因為
`class`會預設所有東西為`private`，也就是在`class`外不能使用

相反的，如果想要保護`class`裡的資料，用`private`就是個不錯的選擇

在加上`public:`之後，上面的程式碼可以順利執行了
輸出如下

i.e.

```
result:
( 2,  5)
(-5,  3)
( 1, -1)
(-4, -1)
( 5,  1)
```

---

## Constructor

在上一節，我用了這個又難看又長的方法來初始化`position`
```cpp=
position pos[5];
pos[0].x =  2, pos[0].y =  5;
pos[1].x = -5, pos[1].y =  3;
pos[2].x =  1, pos[2].y = -1;
pos[3].x = -4, pos[3].y = -1;
pos[4].x =  5, pos[4].y =  1;
```
想當然一般來說不可能會這樣用，既慢又沒有效率

那有什麼解決方法呢?

其中一個就是建構元(Constructor)

建構元正如其名，是在一個class要**建構**的時候呼叫的函式

同樣以直角坐標的點來舉例

i.e.
```cpp=
class position{
public:
    int x;
    int y;
    
    position(int a, int b){
        x = a, y = b;
    }
};
```

其中
```cpp
position(int a, int b){
    x = a, y = b;
}
```
的部分就是建構元

建構元並沒有回傳值，且在宣告時的名字要和`class`的名字一致

這樣一來，上述那個很長的初始化就可以縮短

e.g.
```cpp=
#include<iostream>
#include<iomanip>
using namespace std;
class position {
public:
	int x;
	int y;
	position(int a, int b) {
		x = a, y = b;
	}
};
int main() {
	//{ 2,  5},{-5,  3},{ 1, -1},{-4, -1},{ 5, 1}
	position pos[5] = {
		position( 2,  5), 
		position(-5,  3), 
		position( 1, -1), 
		position(-4, -1), 
		position( 5,  1)
	};
	for (int i = 0; i != 5; i++)
		cout << "(" << setw(2) << pos[i].x << ", " << setw(2) << pos[i].y << ")" << endl;
	return 0;
}
```

或者是在定義的地方可以用建構元專用的初始化敘述
例如上面那個建構元就可以改寫成這種形式

i.e.
```cpp=
position(int a, int b): x(a), y(b) {}
```

:::success
**隨堂測驗**
有什麼情況是非得用這種初始化敘述的呢? (提示，宣告後不能改變)
:::


以為這樣就結束了嗎

不，顯然否

其實在C++11以後，新增了一個叫做`initializer_list`的東西
這種容器的應用很廣泛，在`class`上，我們可以用它來做非常簡單的初始化

i.e.
```cpp=
#include<iostream>
#include<iomanip>
using namespace std;
class position {
public:
	int x;
	int y;
};
int main() {
	//{ 2,  5},{-5,  3},{ 1, -1},{-4, -1},{ 5, 1}
	position pos[5] = {{ 2,  5}, { -5,  3}, { 1, -1}, { -4, -1}, { 5, 1}};
	for (int i = 0; i != 5; i++)
		cout << "(" << setw(2) << pos[i].x << ", " << setw(2) << pos[i].y << ")" << endl;
	return 0;
}
```

像這樣用大括號包住一些值，初始化時就會依序對應到`class`裡的變數

:::success
**延伸思考** 
如果大括號裡的值比宣告的變數少的話會發生什麼事，如果比較多又會發生什麼事?
:::

---

## Destructor

有好好背4000單的同學應該就會知道
construction <--> destruction

那相對於建構元(Constructor)
當然就會有解構元(Destructor)的存在

接下來介紹幾個解構元的需要注意的點
* 解構元在`class`被解構的時候才會呼叫
* 解構元沒有回傳值也沒有引數

那接下來就是示範解構元如何用

e.g.
```cpp=
class position {
public:
	int x;
	int y;
    ~position(){}
};
```

就這樣，沒了💩

看起來好像很沒用

我可以肯定的說，~~真的沒用~~

.
.
.

在某些地方真的是有用的
主要會有差的地方其實是在`class`裡有指標的時候
為了釋放`class`內指標占用的記憶體
可以在解構元寫`delete`

:::success
**動動腦**
還有哪些地方用的到解構元呢?
:::

---

## Member function

一樣是那個直角坐標上的五個點

如果今天需要對點做一個操作，要把`x`跟`y`做對換
可以這樣寫

i.e.
```cpp=
class position {
public:
	int x;
	int y;
};
void swap(position& pos){
    int tmp = pos.x;
    pos.x = pos.y;
    pos.y = tmp;
}
```

這種寫法雖然很直覺，不過如果`x`跟`y`兩個變數都是`private`的話就會有些問題

e.g.
```cpp=
class position {
	int x;
	int y;
};
void swap(position& pos){
    int tmp = pos.x;
    pos.x = pos.y;
    pos.y = tmp;
}
```

:::danger
error: 'int position::x' is private within this context
error: 'int position::y' is private within this context
:::

如果你`x`跟`y`需要設成`private`
但需要對這些變數進行一些自定義的操作時
這時候用`public`的成員函數就可以解決上面的編譯錯誤
同時限定外部只能透過這個函式來操作變數值
會很大部分避免不知道哪裡直接修改到變數值然後debug不出來的問題

成員函數的宣告方法跟一般函數無異
不過在成員函數內可以直接取用`class`的成員變數和其他成員函數

講了這麼多，直接上code

i.e.

```cpp=
#include<iostream>
using namespace std;
class position {
	int x;
	int y;
public:
	void swap() {
		int tmp = x;
		x = y;
		y = tmp;
	}
	void print() {
		cout << x << ' ' << y << endl;
	}
	position(int a, int b): x(a), y(b) {};
};
int main() {
	position pos{2, 5};
	pos.print();
	pos.swap();
	pos.print();
	return 0;
}
```

```
result:
2 5
5 2
```

:::success
**想想看**
明明上面小節用`initializer_list`沒有寫，為什麼還要再寫一個建構元呢
:::

---

## Difference between Class and Struct

在舊版本的C語言中，`Struct`和`Class`其實有明顯的差別
不過到了C++，兩者最顯著的差別就只剩下
* `Class`預設`private`
* `Struct`預設`public`

什麼意思呢
直接上code

```cpp=
#include<iostream>
using namespace std;
class position {
	int x;
	int y;
};
int main() {
	position pos{2, 5};
	cout << pos.x << ' ' << pos.y << endl;
	return 0;
}
```

:::danger
error: 'int position::x' is private within this context
error: 'int position::y' is private within this context
:::

由於前幾節已經討論過這個情形
很明顯可以看出問題在於`position`裡沒有加上`public`

不過當我們改用`struct`
不用改任何其他code也不會有編譯錯誤

```cpp=
#include<iostream>
using namespace std;
struct position {
	int x;
	int y;
};
int main() {
	position pos{2, 5};
	cout << pos.x << ' ' << pos.y << endl;
	return 0;
}
```

```
result:
2 5
```

:::success
**調查**
你比較喜歡用class還是struct呢，可以善用HackMD的留言功能跟我們說喔
:::

---

## Inheritance

這屬於比較進階的用法

學到三角函數之後，極座標就是一個我們會碰到，有別於直角坐標系的另一種表示方法

那如果今天兩種表示方法都要用到
我們當然可以用兩種`class`來裝這兩種不同的表示方法

但是有些不管是直角座標或極座標都會用到的函數
寫兩次就感覺有點太過多餘

這種時候就可以用到`class`的**繼承**(Inheritance)

直接上code

i.e.
```cpp=
#include<iostream>
using namespace std;
struct position {
	void print() {
		cout << "this is a point" << endl;
	}
};
struct polar: position {
	float r;
	float theta;
	polar(float a, float b): r(a), theta(b) {}
};
struct cartesian: position {
	float x;
	float y;
	cartesian(float a, float b): x(a), y(b) {}
};
void wow(position pos) {
	cout << "wow, a point" << endl;
}
int main() {
	cartesian pos{2, 5};
	pos.print();
	wow(pos);
	wow(polar(1, 2));
	return 0;
}
```
```
result:
this is a point
wow, a point
wow, a point
```

可以發現不管是`cartesian`或是`polar`都屬於`position`
所以`position`內定義的函式在`cartesian`和`polar`都可以用

同樣的使用`position`當作引數的外部函式
`cartesian`和`polar`也都可以用

:::success
**腦力激盪**
還有什麼情況很適合用繼承來表示呢?
:::

---

> [color=#1f1e33][name=施羿廷][time=Fri, Jun 19, 2020 10:37 PM]