---
# System prepended metadata

title: 15 使用 class 彰顯物件導向的意義
tags: [Arduino, CPP]

---

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