###### 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` 這兩個成員來控制迴圈, 製造我們想要的呼吸燈效果。