###### tags: `CPP` `Arduino`
# 14 邁向物件導向程式設計 (object-oriented programming)
目前的程式閃爍 LED 的**設定資料** (總時長、亮燈時長、切換次數) 和**功能** (`twinkle` 函式) 是分離的, 不過其實結構提供有整合資料與功能的機制, 可以讓我們將兩者合併在一起。以下我們先來看看修改版的程式:
```cpp=
const int led_pin = 5;
struct twinkle_data {
int pin; // LED 腳位
int duration; // 亮滅總時長
int duration_on; // 亮燈時長 (us)
int total; // 切換次數
void twinkle() {
for (int count = 0; count < total; count++) {
digitalWrite(pin, LOW);
delayMicroseconds(duration_on);
digitalWrite(pin, HIGH);
delayMicroseconds(duration - duration_on);
}
}
};
twinkle_data twinkle_datas[] = {
{led_pin, 100, 5, 10000}, // LED 稍亮 1s
{led_pin, 100, 30, 5000}, // LED 稍亮 0.5s
{led_pin, 100, 100, 2000} // LED 全亮 0.2s
};
void setup() {
pinMode(led_pin, OUTPUT);
}
void loop()
{
for (int i = 0; i < sizeof(twinkle_datas) / sizeof(twinkle_data); i++)
{
twinkle_datas[i].twinkle();
}
}
```
1. 第 9~16 行是把原本的 `twinkle` 函式搬到結構中, 這樣樣做的好處是函式跟資料整合在一起, 實質上這帶來了幾個改變:
- 在函式中可以直接**用成員的名稱取得資料**, 像是第 10 行就直接取得切換次數 `total`。
- 由於可以直接取得資料, 所以呼叫函式時並不需要傳入額外的資料, 因此**沒有任何參數**。
2. 這樣的寫法使得 `twinkle` 函式也變成是結構的成員, 所以要呼叫時也跟取用成員資料時一樣採用 `.` 語法, 像是第 33 行這樣。這種放在結構中的函式就稱為**成員函式 (member function)**。
這樣的寫法讓資料與使用資料的函式緊密的整合在一起, 此時, `twinkle_data` 抽象上看起來就像是一個**實體的 LED 電子元件**, 具備閃爍 (twinkle) 的功能, 而且可以自訂閃爍的時間變化與次數。這種**以抽象形式描述實體物件**的程式寫法, 稱為**物件導向程式設計 (object-oriented programming)**。
## 利用建構函式 (constructor) 檢查設定資料
目前程式還有一個可以改進的地方, 在設定閃爍資料的時候, 我們其實都沒有檢查, 如果設定為錯誤的資料, 例如亮燈時長比總時長要長, 那計算出來的熄燈時長就會是負數。結構提供有**建構函式 (constructor)**, 可以在定義結構資料時進行必要的處理, 包括檢查資料等等。以下是我們加上建構函式的版本:
```cpp=
const int led_pin = 5;
struct twinkle_data {
int pin; // LED 腳位
int duration; // 亮滅總時長
int duration_on; // 亮燈時長 (us)
int total; // 切換次數
twinkle_data(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);
}
}
};
twinkle_data twinkle_datas[] = {
twinkle_data(led_pin, 100, 5, 10000), // LED 稍亮 1s
twinkle_data(led_pin, 100, 30, 5000), // LED 稍亮 0.5s
twinkle_data(led_pin, 100, 100, 2000) // LED 全亮 0.2s
};
void setup() {
}
void loop()
{
for (int i = 0; i < sizeof(twinkle_datas) / sizeof(twinkle_data); i++)
{
twinkle_datas[i].twinkle();
}
}
```
1. 第 9~16 行就是建構函式, 你可以看到它與結構同名, 而且不用標註傳回值, 其餘就像是一般函式一樣。
2. 在 10~14 行使用特別的**三元條件 (Ternary conditional) 運算**檢查傳入的參數, 這種運算的寫法如下:
```cpp
(cond) ? a : b
```
`cond` 是一個判斷式, 如果判斷成立, 運算結果就是 `a`, 否則就是 `b`。以第 10 行為例:
```cpp
pin = (_pin <= 0) ? 5 : _pin;
```
如果傳入的 `_pin` 不是正整數, 就把成員 `pin` 設為 LOLIN D32 的內建 LED 腳位 5, 否則就設定為傳入的參數 `_pin` 的值。其他各行都依照相同的方式檢查傳入的參數。第 13 行還特別檢查 `_duration_on` 沒有比 `_duration` 小的時候, 強制設定 `duration_on` 成員的值和 `duration` 成員的值一樣。這樣即使設定的資料錯誤, 也會改成適當的值。
1. 弟 15 行我們把原本在 `setup` 內設定腳位功能的那一行搬過來, 這樣才能讓不同的 `twinkle_data` 結構可以控制不同的腳位, 而不是只能控制同一個腳位。這樣所有控制 LED 所需要的資料與功能全部都包裝在結構中了。
3. 第 29~31 行是改用建構函式來產生結構, 方法就跟呼叫一般的函式一樣。
透過這樣的方式, 就可以避免在產生結構資料時放入不符規定的資料。
## 改寫呼吸燈程式
現在我們就可以回頭改寫呼吸燈程式了, 請看以下的修改結果:
```cpp=
const int led_pin = 5;
struct 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();
}
```
1. 由於現在結構內不只包含資料, 因此我們把結構的名稱改掉, 換成更符合實際物件的 `LED`, 在 C++ 中, 習慣將這種定義有成員函式的結構以大寫字母開頭的單字命名, 不過 LED 本來就是首字母大寫的簡稱, 所以我們就維持全部大寫的 LED。另外要注意的是建構函式也要一併更改名稱。
3. 第 28 行就定義了一個 `LED` 結構變數, 命名為 `led`。
4. 第 35~38 就直接使用 `duration_on` 和 `duration` 這兩個成員來控制迴圈, 製造我們想要的呼吸燈效果。