###### tags: `CPP` `Arduino`
# 15 使用 class 彰顯物件導向的意義
在物件導向程式設計中, 我們稱這種含有函式成員的結構資料為**物件 (object)**, 而這種結構的定義則稱為『**類別 (class)**』。像是剛剛的例子中, `LED` 就是類別, `led` 是物件, 我們也會說 led 是 LED 類別的一個**實例 (instance)**, 表示 led 是依據 LED 類別的定義所產生的一個物件。
在 C++ 中, 可以用 `class` 來定義結構, 更能展現上述類別的意思, 以下就改用 `class` 來改寫剛剛的範例:
```cpp=
const int led_pin = 5;
class LED {
int pin; // LED 腳位
int duration; // 亮滅總時長
int duration_on; // 亮燈時長 (us)
int total; // 切換次數
LED(int _pin, int _duration, int _duration_on, int _total) {
pin = (_pin <= 0) ? 5 : _pin;
duration = (_duration <= 0) ? 5 : _duration;
duration_on = (_duration_on < 0) ? 0 : _duration_on;
duration_on = (_duration_on < _duration) ? _duration_on : duration;
total = (_total < 0) ? 1 : _total;
pinMode(pin, OUTPUT);
}
void twinkle() {
for (int count = 0; count < total; count++) {
digitalWrite(pin, LOW);
delayMicroseconds(duration_on);
digitalWrite(pin, HIGH);
delayMicroseconds(duration - duration_on);
}
}
};
LED led = LED(led_pin, 100, 0, 200);
void setup() {
}
void loop()
{
for (led.duration_on = 0; led.duration_on <= led.duration; led.duration_on++)
led.twinkle();
for (led.duration_on = led.duration; led.duration_on >= 0;led.duration_on--)
led.twinkle();
}
```
`class` 跟 `struct` 基本上是可以劃上等號的, 所以我們只要把 `struct` 換成 `class` 應該就可以了。上傳程式看看, 咦, 出錯了:
```
intro:27:35: error: 'LED::LED(int, int, int, int)' is private within this context
LED led = LED(led_pin, 100, 0, 200);
...
```
他說我們的建構函式是**私有的 (private)**, 這是怎麼一回事呢?
其實類別中的成員有分成**公開 (public)** 和**私有 (private)**, 私有成員只有在成員函式內才能取用, 公開成員則沒有限制, 而 `class` 跟 `struct` 的差別就是沒有特別標示時, **`class` 內的成員都是私有的**, 但是 **`struct` 內的成員卻是公開的**, 因此剛剛的程式裡所有的成員都是私有, 連建構函式也不例外, 而我們呼叫建構函式的地方並不在成員函式內, 因此無法呼叫。
要解決這個問題只要標註成員為公開就可以了, 方法如下:
```cpp=
const int led_pin = 5;
class LED {
public:
int pin; // LED 腳位
int duration; // 亮滅總時長
int duration_on; // 亮燈時長 (us)
int total; // 切換次數
LED(int _pin, int _duration, int _duration_on, int _total) {
pin = (_pin <= 0) ? 5 : _pin;
duration = (_duration <= 0) ? 5 : _duration;
duration_on = (_duration_on < 0) ? 0 : _duration_on;
duration_on = (_duration_on < _duration) ? _duration_on : duration;
total = (_total < 0) ? 1 : _total;
pinMode(pin, OUTPUT);
}
void twinkle() {
for (int count = 0; count < total; count++) {
digitalWrite(pin, LOW);
delayMicroseconds(duration_on);
digitalWrite(pin, HIGH);
delayMicroseconds(duration - duration_on);
}
}
};
LED led = LED(led_pin, 100, 0, 200);
void setup() {
}
void loop()
{
for (led.duration_on = 0; led.duration_on <= led.duration; led.duration_on++)
led.twinkle();
for (led.duration_on = led.duration; led.duration_on >= 0;led.duration_on--)
led.twinkle();
}
```
第 4 行加上了 `public:`, 表示從這裡開始, 以下出現的成員都是公開成員, 這樣我們就可以在任何地方呼叫建構函式, 或是取用成員了。
## 使用私有成員避免出錯
剛剛提到了 `struct` 與 `class` 的差別後, 你一定會想使用 `class` 變成私有成員不是更麻煩, 為什麼要設定為私有成員呢?這主要是私有成員可以**限制存取**, 避免程式**意外修改**成員, 造成錯誤。舉例來說, 在 `LED` 中, 如果把亮燈時長修改為負數或是比總時長還要長, 或是隨意修改總時長, 都會讓閃爍的效果不正確, 為了避免這種狀況, 我們可以把資料成員都設為私有, 另外加上公開的成員函式提供修改亮燈時長和讀取總時長的功能, 像是這樣:
```cpp=
const int led_pin = 5;
class LED {
int pin; // LED 腳位
int duration; // 亮滅總時長
int duration_on; // 亮燈時長 (us)
int total; // 切換次數
public:
LED(int _pin, int _duration, int _duration_on, int _total) {
pin = (_pin <= 0) ? 5 : _pin;
duration = (_duration <= 0) ? 5 : _duration;
set_duraion_on(_duration_on);
total = (_total < 0) ? 1 : _total;
pinMode(pin, OUTPUT);
}
void set_duraion_on(int _duration_on) {
duration_on = (_duration_on < 0) || (_duration_on > duration) ?
0 : _duration_on;
}
int get_duration() {
return duration;
}
void twinkle() {
for (int count = 0; count < total; count++) {
digitalWrite(pin, LOW);
delayMicroseconds(duration_on);
digitalWrite(pin, HIGH);
delayMicroseconds(duration - duration_on);
}
}
};
LED led = LED(led_pin, 100, 0, 200);
void setup() {
}
void loop()
{
for (int i = 0; i <= led.get_duration(); i++) {
led.set_duraion_on(i);
led.twinkle();
}
for (int i = led.get_duration(); i >= 0; i--) {
led.set_duraion_on(i);
led.twinkle();
}
}
```
1. 我們把 `public:` 移到第 9 行, 所以上面的資料成員全部都是預設的私有性質。
2. 第 18~21 行是新增的 `set_duration_on` 函式, 一樣使用三元運算檢查設定值, 不過在判斷條件中使用了 `||` 結合設定值是負數或者設定值大於總時長兩個條件, `||` 會在兩個條件任一個成立時運算結果就成立, 兩個條件都不成立時運算結果才會是不成立。
3. 第 13 和 45、49 行現在就改成直接呼叫 `set_duration_on` 來設定亮燈時長。
4. 第 23~25 則是新增用來讀取總時長的函式, 由於沒有提供設定總時長的函式, 所以一旦建立 `LED` 類別的物件後, 就無法再修改總時長了。
5. 第 44、48 也改用 `get_duration` 來取得總時長。
雖然修改後的程式變長了, 好像也變囉唆了, 但你可以看到透過私有與公開的成員, 我們就可妥善控管成員的存取, 降低後續程式意外修改成員出錯的可能性。
:::info
除了私有與公開以外, C++ 還有其他控制成員存取的等級, 有興趣的人可以參考相關教材。
:::