---
# System prepended metadata

title: 11 使用指位器 (pointer) 提升效率
tags: [Arduino, CPP]

---

###### tags: `CPP` `Arduino`

# 11 使用指位器 (pointer) 提升效率

程式到目前似乎很完美, 不過還藏有一個小缺點, 結構在傳送給函式時是用複製的方式製作副本給函式, 這表示當呼叫函式時, 會佔用雙倍的空間, 可是當函式執行完, 複製的副本就會被丟棄。這就像是我要跟你借書, 但你卻影印一本給我, 我看完後又把影本丟掉。這樣的作法有以下缺點：

1. 製作副本需要**時間**複製資料, 就像是影印一本書也需要時間, 書越厚越耗時, 結構裡的成員越多時複製資料的時間也越久。像是控制 LED 亮度就必須依賴時間, 如果過程中有其他耗時的動作, 就可能影響實際的效果。
2. 副本會佔用**空間**, 甚至有可能因為剩餘的空間不夠, 導致無法建立副本而沒辦法執行程式。

要解決上述問題, 可以採用**傳送位址**的方式, 讓函式去取用**同一份**資料, 而不是建立副本, 就像是直接告訴我要借的書放在哪裡讓我去翻閱, 而不是影印一本給我。

我們可以把原來的程式改成這樣：

```cpp=
const int led_pin = 5;

struct twinkle_data {
  int pin;                // LED 腳位
  int duration;           // 亮/熄總時長
  int duration_on;        // 亮燈時長 (us)
  int total;              // 切換次數
}; 

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 twinkle(twinkle_data *data) {
  for(int count = 0;count < (*data).total; count++) {
    digitalWrite((*data).pin, LOW);
    delayMicroseconds((*data).duration_on);
    digitalWrite((*data).pin, HIGH);
    delayMicroseconds((*data).duration - (*data).duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  for(int i = 0; i < sizeof(twinkle_datas)/sizeof(twinkle_data); i++) {
    twinkle(&twinkle_datas[i]); 
  }
}
```

1. 第 16 行在 `data` 前面的 **`*`** 就表示這個參數是接收資料所在的位置, 而不是資料的副本, 這種加上 `*` 符號的變數稱為**指位器 (pointer)**, 因為變數內存放的不是實際的資料, 而是**資料所在的位置**。前面的 `twinkle_data` 表示這個指位器指向的位置是一個 `twinkle_data` 結構。
2. 第 17~21 行中 **`*data`** 表示要**透過指位器紀錄的位置取用資料**, 以本例來說 `*data` 就是 `data` 指向的那一個 `twinkle_data` 結構。因此 `(*data).total` 就是取得 `twinkle_data` 的 `data` 成員。 

    :::danger
    要特別注意的是, `(*data).total` 不能省略小括號寫成 `*data.total`, 否則會出現錯誤：

    ```
    intro.cpp:17:35: error: request for member 'total' in 'data', which is of pointer type 'twinkle_data*' (maybe you meant to use '->' ?)
       for(int count = 0;count < *data.total; count++) {
                                       ^~~~~
    ```

    這是因為在 C++ 中, `.` 的運算順序比 `*` 高, 所以會先處理 `data.total`, 但是 `data` 是指位器, 並不是結構, 所以無法取得成員。
    :::

1. 如果你覺得 `(*data).total` 寫起來很麻煩, C++ 對於透過指位器取用結構成員有另一個簡便的寫法, 像是這樣：

```cpp=
data->total
```

我們可以依此寫法修改程式如下：

```cpp=
const int led_pin = 5;

struct twinkle_data {
  int pin;                // LED 腳位
  int duration;           // 亮/熄總時長
  int duration_on;        // 亮燈時長 (us)
  int total;              // 切換次數
}; 

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 twinkle(twinkle_data *data) {
  for(int count = 0;count < data->total; count++) {
    digitalWrite(data->pin, LOW);
    delayMicroseconds(data->duration_on);
    digitalWrite(data->pin, HIGH);
    delayMicroseconds(data->duration - data->duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  for(int i = 0; i < sizeof(twinkle_datas)/sizeof(twinkle_data); i++) {
    twinkle(&twinkle_datas[i]); 
  }
}
```

## 透過指位器修改資料的注意事項

你可能會想說可不可以再簡化程式, 像是在 `twinkle` 函式內的迴圈, 如果直接用結構的 `total` 來倒數, 是不是就不需要增加 `count` 變數了？我們來試試看：

```cpp=
const int led_pin = 5;

struct twinkle_data {
  int pin;                // LED 腳位
  int duration;           // 亮/熄總時長
  int duration_on;        // 亮燈時長 (us)
  int total;              // 切換次數
}; 

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 twinkle(twinkle_data *data) {
  for(;data->total > 0; data->total--) {
    digitalWrite(data->pin, LOW);
    delayMicroseconds(data->duration_on);
    digitalWrite(data->pin, HIGH);
    delayMicroseconds(data->duration - data->duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  for(int i = 0; i < sizeof(twinkle_datas)/sizeof(twinkle_data); i++) {
    twinkle(&twinkle_datas[i]); 
  }
}
```

注意到第 17 行我們把初始設定拿掉, 所以 `for` 小括號內第 1 個分號前是空的, 接著用 `total` 成員當判斷條件, 只要還沒倒數到 0 就繼續, 並在變化動作裡遞減 `total` 成員的值。

你可以上傳程式試看看, 會發現亮度變化一輪後, 就不會再亮了。這是因為 `twinkle` 函式內取用的結構就是 `twinkle_datas` 陣列的內容, `for` 執行完 `total` 成員就變成 0 了, 下一次再進入 `twinkle` 時, `for` 的判斷條件一開始就不成立, 完全不會進行亮/熄切換, 自然就看不到亮燈了。