第4回
配列・メモリ・ポインタ・参照
C++で頻繁に登場するメモリとアドレスの基本を理解する。
配列
// 5つの整数を格納する配列
int values[5];
values[0] = 10;
values[1] = 20;配列とループ
// values[0]~values[4]を表示する
for (int i = 0; i < 5; i++) {
Serial.println(values[i]);
}配列の要素をループで処理する際、添字の範囲を注意する。
メモリ
変数 ↓ メモリ上の領域
変数には「値」だけでなく「どこに保存されているか」というアドレスがある。
アドレス演算子
int value = 10;
int* ptr = &value;&valueはvalueのアドレス。int*は「intを指すポインタ」という型である。ptrはvalueのアドレスを保持するポインタである。
デリファレンス
*ptr = 20;ptrが指している場所の値を変更する。この行のあとvalueは20になっている。
ポインタのインクリメント
| 意味 | |
|---|---|
ptr++ | ポインタを1つ進める |
*ptr++ | 指している値を取得してからポインタを1つ進める |
(*ptr)++ | 指している値を1つ増やす |
参照
int value = 10;
int& ref = value;refはvalueの別名として扱える。ref = 20;と書けばvalueが変わる。
ポインタと参照の比較
| ポインタ | 参照 | |
|---|---|---|
| アドレスを保持 | ○ | 内部的に利用 |
nullptr | ○ | × |
| 再代入 | ○ | 基本的に× |
*が必要 | ○ | × |
動かして確かめる
演習に入る前に、まずこのサンプルを動かして結果を見る。追加の部品は要らない。光るのはボードに載っている内蔵LEDで、それ以外はシリアルモニタの出力で確かめられる。code/examples/04-pointers/を開き、pio run -t uploadで書き込んでからpio device monitorで出力を見る(手順と、外付けのLEDなどをつなぐ場合は開発環境にまとめてある)。
/**
* @brief ポインタ経由で呼び出し元の変数を1増やす
* @param[in,out] value 増やす変数へのポインタ
*/
void addOne(int* value) {
*value = *value + 1;
}
/**
* @brief 参照経由で呼び出し元の変数を1増やす
* @param[in,out] value 増やす変数への参照
*/
void addOneRef(int& value) {
value = value + 1;
}
/**
* @brief 値をコピーして受け取り、1増やす(呼び出し元は変わらない)
* @param value 増やす値
*/
void addOneCopy(int value) {
value = value + 1;
} Serial.println("--- アドレスと値 ---");
int value = 10;
int* ptr = &value;
Serial.print("value = ");
Serial.println(value);
Serial.print("&value = 0x");
Serial.println((uintptr_t)&value, HEX);
Serial.print("ptr = 0x");
Serial.println((uintptr_t)ptr, HEX); // &value と同じ
Serial.print("*ptr = ");
Serial.println(*ptr); // value と同じ
*ptr = 20;
Serial.print("*ptr = 20 のあと value = ");
Serial.println(value); // 20
Serial.println("--- 参照 ---");
int& ref = value;
ref = 30;
Serial.print("ref = 30 のあと value = ");
Serial.println(value); // 30
Serial.println("--- 関数に渡す ---");
int n = 0;
addOneCopy(n);
Serial.print("addOneCopy の後 : ");
Serial.println(n); // 0(コピーを増やしただけ)
addOne(&n);
Serial.print("addOne の後 : ");
Serial.println(n); // 1
addOneRef(n);
Serial.print("addOneRef の後 : ");
Serial.println(n); // 2
}
void loop() {
}--- 配列 ---
values[0] = 120
values[1] = 340
values[2] = 95
values[3] = 870
values[4] = 410
max = 870
--- アドレスと値 ---
value = 10
&value = 0x4085FD1C
ptr = 0x4085FD1C
*ptr = 10
*ptr = 20 のあと value = 20
--- 参照 ---
ref = 30 のあと value = 30
--- 関数に渡す ---
addOneCopy の後 : 0
addOne の後 : 1
addOneRef の後 : 2確かめること。
&valueとptrが同じ値を表示している。ポインタは「どこにあるか」を持っている- アドレスの数値そのものは実行ごと・環境ごとに変わる。意味があるのは「2つが一致すること」だけ
addOneCopyではnが変わらない。コピーを1増やして捨てているaddOne(ポインタ)とaddOneRef(参照)はどちらもnを変える。呼び出し側に&が要るかどうかだけが違う
演習
- 配列の最大値を求める
- ポインタを使って関数から変数を変更する
- 参照を使って関数から変数を変更する
void addOne(int* value) {
*value = *value + 1;
}
void addOneRef(int& value) {
value = value + 1;
}