owned this note
owned this note
Published
Linked with GitHub
---
tags: books, note
---
# Class
如果今天在直角座標平面上有幾個點需要表示

學`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]