C++: ポインタと配列
最終更新:2026-08-31
レッスン16で配列を学びましたが、実は配列とポインタには深い関係があります。
実は、配列名はポインタ —— 配列の先頭要素のアドレスを指します。
この関係を理解すると、配列を「ポインタ視点」で扱えるようになります。
1. 配列とポインタの関係
(1) 1.1 基本的な関係
配列名はポインタです。
CPP
#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;
// 同じアドレスが出力される! arr と &arr[0] は同じ
return 0;
}
出力例:
TEXT 📖 参照専用arr = 0x7ffd4a3b &arr[0] = 0x7ffd4a3b
💡 ヒント:
arr と &arr[0] は同じアドレスを指します。
2. ポインタで配列にアクセス
(1) 2.1 基本的な使い方
CPP
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // pは配列の先頭を指す
// 方法1:添字演算子(推奨)
std::cout << "arr[0] = " << p[0] << std::endl; // 10
std::cout << "arr[1] = " << p[1] << std::endl; // 20
// 方法2:ポインタ演算
std::cout << "*p = " << *p << std::endl; // 10
std::cout << "*(p+1) = " << *(p+1) << std::endl; // 20
return 0;
}
💡 ヒント:
p[i] と *(p+i) は等価です(ポインタ演算)。
3. ポインタ演算
(1) 3.1 ポインタの加算
ポインタに整数を加えると、アドレス値に n × sizeof(型) バイト加算されます。
CPP
#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つのポインタの差は、要素数(インデックスの差)になります。
CPP
#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. ポインタで配列を走査(反復)
▶ サンプル 1:配列の走査(難易度 ⭐)
CPP
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 方法1:添字(初心者向け)
std::cout << "方法1(添字):" << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 方法2:ポインタ
std::cout << "方法2(ポインタ):" << std::endl;
for (int* p = arr; p < arr + 5; p++) {
std::cout << *p << " ";
}
std::cout << std::endl;
// 方法3:std::begin/end(推奨)
// std::cout << "方法3:std::begin/end" << std::endl;
// for (int* p = std::begin(arr); p != std::end(arr); p++) {
// std::cout << *p << " ";
// }
return 0;
}
出力:
TEXT 📖 参照専用方法1(添字): 10 20 30 40 50 方法2(ポインタ): 10 20 30 40 50
💡 ヒント: 通常は方法1(添字)を使いましょう。ポインタ走査は内部動作を理解するのに役立ちます。
5. 配列を関数に渡す
(1) 5.1 問題:配列はポインタとして渡される
配列を関数に渡すと、配列はポインタに変換されます(先頭要素へのポインタ)。
CPP
#include <iostream>
void printSize(int arr[]) {
// arrはポインタ、配列ではない!
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 8(64ビット、ポインタのサイズ)
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 20(5 × 4 = 20バイト)
printSize(arr);
return 0;
}
💡 ヒント:
printSize 関数内の arr はポインタなので、sizeof(arr) はポインタのサイズ(8バイト)になり、配列全体のサイズ(20バイト)にはなりません。
(2) 5.2 解決策:配列サイズを渡す
CPP
#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. 実践例:配列の反転
▶ サンプル 2:ポインタで配列を反転(難易度 ⭐⭐)
CPP
#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;
}
出力:
TEXT 📖 参照専用反転前:10 20 30 40 50 反転後:50 40 30 20 10
7. よくあるエラー
(1) 7.1 ポインタの範囲外アクセス
CPP
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;
std::cout << *(p + 10) << std::endl; // ❌ 範囲外!p+10は配列外
// 未定義動作、予測不能な結果
return 0;
}
💡 ヒント: C++はポインタの境界チェックをしない!コンパイルは通りますが、重大なバグになります。
(2) 7.2 無関係なポインタ同士の減算
CPP
#include <iostream>
int main() {
int x = 5;
int y = 10;
int* p1 = &x;
int* p2 = &y;
std::cout << p2 - p1 << std::endl; // ❌ 未定義動作!p1とp2は同じ配列内にない
// 意味のない結果
return 0;
}
▶ サンプル 3:ポインタで配列サイズを計算(難易度 ⭐⭐)
CPP
#include <iostream>
int main() {
int data[] = {5, 10, 15, 20, 25};
// ポインタ演算で配列サイズを計算
int* begin = data;
int* end = data + 5; // 最後の要素の次
size_t size = end - begin;
std::cout << "配列サイズ: " << size << std::endl;
// ポインタで逆順に反復
std::cout << "逆順: ";
for (int* p = end - 1; p >= begin; p--) {
std::cout << *p << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用配列サイズ: 5 逆順: 25 20 15 10 5
❓ よくある質問
Q
arr と &arr は値が同じだけど型が違う?A はい!
arrの型はint*(intへのポインタ)&arrの型はint(*)[5](「5つのintの配列」へのポインタ)
値は同じ(先頭アドレス)ですが、型が違う
Q
sizeof(arr) が main では20なのに、関数では8なのはなぜ?A
main では arr は配列として扱われますが、関数内では arr はポインタとして扱われます。
解決策:
📖 まとめ
- 配列名はポインタ(先頭要素のアドレス)
- ポインタの加算は、
n × sizeof(型)バイト進む - ポインタの減算は、要素数の差を返す
- 配列を関数に渡すとポインタになる
- ポインタで配列を操作:添字、ポインタ演算、標準関数など
📝 練習問題
-
初級(難易度 ⭐):
int配列arr[5] = {1, 2, 3, 4, 5}を作り、int*ポインタpでarrを指してください。ポインタで全要素を出力してください。 -
中級(難易度 ⭐⭐): 関数
int* findElement(int* arr, int size, int target)を書いてください。配列から値を検索し、見つかったらそのポインタを返す、なければnullptrを返す。 -
上級(難易度 ⭐⭐⭐): プログラムを書いてください。ポインタで「バブルソート」を実装:
- 配列を入力
- 昇順にソート
- ソート前後を表示
8. 🚀 次のステップ
ポインタと配列の関係を理解したら、次は ポインタ応用(レッスン24) —— ポインタの詳細な型、void* ポインタ、ポインタ配列を学ぼう!