###### 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++ 還有其他控制成員存取的等級, 有興趣的人可以參考相關教材。 :::