ポインタと配列
[第]16[課で]arrayについて学びましたが、[array]が[一体何なのか]については[説明しませんでした]。
[実は]、配列とは——[そこに格納されているのは]配列の[先頭要素の]アドレスです。
[この点を理解すれば]、[なぜ]array[が引数として使われると]「ポインタに『退化』するのか」が理解できる。
1. array[名]はポインタである
(1) 1.1 [基本的事実]
array[名]は、先頭の要素を指すポインタです。
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "arr = " << arr << std::endl;
std::cout << "&arr[0] = " << &arr[0] << std::endl;
// output! arr &arr[0]
return 0;
}
出力(例):
arr = 0x7ffd4a3b
&arr[0] = 0x7ffd4a3b
💡 [重要]: arr [と] &arr[0] [は][同じ]アドレスです。
2. [ポインタ]を使って[配列]の[要素]に[アクセス]する
基本的な使い方
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // p
//:()
std::cout << "arr[0] = " << p[0] << std::endl; // 10
std::cout << "arr[1] = " << p[1] << std::endl; // 20
//:
std::cout << "*p = " << *p << std::endl; // 10
std::cout << "*(p+1) = " << *(p+1) << std::endl; // 20
return 0;
}
💡 [要点]: p[i] [つまり] *(p+i)([後述する]ポインタ[演算])。
3. ポインタ[算術](Pointer Arithmetic)
(1) 3.1 ポインタ[整数の加算・減算]
ポインタ[整数を加減算する場合]、[単にアドレス値を加算・減算するのではなく]、[整数] × sizeof(type) [個のバイト]を加減算する。
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // arr[0]
std::cout << "p = " << p << std::endl; // 0x1000
std::cout << "p + 1 = " << p + 1 << std::endl; // 0x1004( 0x1001!)
std::cout << "p + 2 = " << p + 2 << std::endl; // 0x1008
return 0;
}
[原理]:
int[占] 4 [バイト]p + 1[実際の加算値]1 × 4 = 4[バイト数]- [したがって]
p + 1[指向]arr[1]
(2) 3.2 ポインタ[減算]
[2つの]ポインタを[引き算すると]、[得られるのはそれらの間の距離]([要素の数])であり、[バイト数]ではない。
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0]; // arr[0]
int* p2 = &arr[3]; // arr[3]
std::cout << "p2 - p1 = " << p2 - p1 << std::endl; // 3()
return 0;
}
💡 [要点]: [[同じ]配列[を指す]ポインタ同士を[引き算すること]にのみ意味がある]。
4. [ポインタを使って]配列を[走査する]([3つの方法])
▶ サンプル 1: [配列の走査] (難易度 ⭐)
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
//:()
std::cout << ":" << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
//:
std::cout << ":" << std::endl;
for (int* p = arr; p < arr + 5; p++) {
std::cout << *p << " ";
}
std::cout << std::endl;
//:()
// std::cout << ":std::begin/end" << std::endl;
// for (int* p = std::begin(arr); p != std::end(arr); p++) {
// std::cout << *p << " ";
// }
return 0;
}
出力:
10 20 30 40 50:
10 20 30 40 50
💡 [おすすめ]: [方法1]([添字])[最も直感的]で、[優先して使う]。[方法2](ポインタ[算術演算])[低レベル]のコード[でよく見られる]。
5. array[引数としての退化]
(1) 5.1 [問題]:配列[はポインタに]退化してしまう
[arrayを]function[に渡すと]、arrayは[ポインタに][変換され]([サイズ情報が失われる])。
#include iostream
void printSize(int arr) {
// arr pointer,array!
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 8(64system,pointer)
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 20(5 × 4 = 20 lessons of)
printSize(arr);
return 0;
}
💡 [要点]: printSize 関数[内の] arr [は実際には]ポインタであり、[したがって] sizeof(arr) [は]ポインタ[のサイズ](8 [バイト])であり、[配列の]サイズ(20 [バイト])[ではない]。
(2) 5.2 [解決方法]:[引数]array[サイズ]
#include iostream
// ...
void printArray(int arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
printArray(arr, 5); // ()
return 0;
}
💡 [ヒント]: [後ほど学習する] std::array [および] std::vectorは、[サイズ情報を保持できる]ため、[より安全]です。
6. 演習:[反転]array
▶ サンプル 2: [用]ポインタ[反転]配列 (難易度 ⭐⭐)
#include iostream
void reverseArray(int* arr, int size) {
int* left = arr; // ...
int* right = arr + size - 1; // ...
while (left < right) {
// left right
int temp = *left;
*left = *right;
*right = temp;
left++; // ...
right--; // ...
}
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << ":";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
reverseArray(arr, 5);
std::cout << ":";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
出力:
20 30 40 50:50 40 30 20 10
7. [よくある]エラー
(1) 7.1 ポインタ[範囲外アクセス]
#include iostream
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;
std::cout << *(p + 10) << std::endl; // ❌!p+10 array
// output,
return 0;
}
💡 [ヒント]: C++では、[ポインタの範囲外アクセス]は自動的にチェックされません![これが] C++の[高速さの理由の一つ]ですが、[同時によくある]バグの[原因]でもあります。
(2) 7.2 ポインタ[引き算は意味をなさない]
#include iostream
int main() {
int x = 5;
int y = 10;
int* p1 = &x;
int* p2 = &y;
std::cout << p2 - p1 << std::endl; // ❌ Definebehavior!p1 and p2 array
// ...
return 0;
}
❓ よくある質問
arr [と] &arr [の]値[は同じ]なのに、[型][が違う]のですか?arrの型はint*(intを[指す]ポインタ) > - &arrの型はint(*)[5]([「5個のintを含む配列」を][指す]ポインタ)* [和] / [ですか]?p + n、p - n) > - [2つの]pointer[の引き算](p2 - p1) > - [大小の比較](p1 < p2)📖 まとめ
- 配列[とは]ポインタ([先頭要素を指す]アドレス)
- ポインタ[整数の加算・減算を行う際]、[加算されるのは]
[整数] × sizeof(type)[バイト] - [2つの]ポインタを[引き算すると]、[得られるのは][要素の数]であり、[バイト数ではない]
- 配列は[引数として渡されると][ポインタに][変換される]ため、[サイズを別途指定する必要がある]
- [ポインタ]を使って[配列]を[走査]するには[3つの方法]がある:[インデックス]、[ポインタ演算]、[標準ライブラリの関数]
📝 練習問題
-
初心者(難易度 ⭐): [1つ宣言]
int配列arr[5] = {1, 2, 3, 4, 5}、[さらに1つ宣言]int*ポインタp[が指す先]arr。[ポインタ] を使用して [すべての要素] を出力する。 -
中級(難易度 ⭐⭐): [関数]
int* findElement(int* arr, int size, int target)を作成し、[配列] の中から目的の [値] を検索する。[見つかった場合]、[その要素を指す] ポインタを返す。[見つからなかった場合]、nullptrを返す。 -
上級(難易度 ⭐⭐⭐): [「バブルソート」を]ポインタ[を使って][実装する]プログラムを[作成する]:
-
[配列を走査し]、[隣接する要素を比較する]
-
[順序が間違っている場合は]、[それらを入れ替える]
-
[この手順を繰り返す]、[配列が[整列する]まで]
-
array[名前]constantpointer([先頭要素]のアドレス)
-
ポインタ[算術]:p + n [オフセット] n [個の要素]、[n] [バイト] ではない
-
配列[インデックスによるアクセス] arr[i] [同等] *(arr + i)
-
ポインタ[配列の走査]配列[インデックス指定より柔軟]
-
配列[として]関数[の引数が]ポインタに退化
8. 🚀 次は
[ポインタと配列]を学んだので、[次は] ポインタの算術演算の応用編([第]24[課])—— [ポインタの]型、void* ポインタ、[および多重]ポインタについて[深く理解]します。



