C++のポインタ演算と配列の関係を徹底解説!初心者が挫折しないためのガイド
生徒
「先生、C++を勉強していると『ポインタ』と『配列』がすごく似ているように見えるんですけど、何か関係があるんですか?」
先生
「鋭いですね!実はC++において、配列とポインタは切っても切れない深い関係にあるんです。特に『ポインタ演算』を知ると、配列の仕組みがより深く理解できますよ。」
生徒
「ポインタを足したり引いたりするってことですか?難しそう……。」
先生
「大丈夫です。一歩ずつ、図解するように丁寧に説明していきますね。まずは基本から見ていきましょう!」
1. 配列は「メモリ上の連続したお部屋」
プログラミング未経験の方にとって、まず「メモリ」という言葉が難しく感じるかもしれません。メモリとは、パソコンが一時的にデータを保存しておくための「作業机」や「棚」のようなものです。
C++の配列(はいれつ)は、その棚の中に、同じ大きさの箱を「隙間なく、順番に」並べた状態のことを指します。
例えば、int numbers[3]; と宣言すると、整数(int型)を入れるための箱が3つ、メモリ上に一列に並んで用意されます。
配列の名前(この場合は
numbers)は、実はその「一番先頭の箱の住所(アドレス)」を指し示しているのです。
2. ポインタ演算とは?(住所の足し算)
ポインタとは、データの「値」そのものではなく、データが保存されているメモリ上の「住所(アドレス)」を格納する専用の変数です。 そして、この住所に対して足し算や引き算を行うことをポインタ演算(えんざん)と呼びます。
「住所に足し算をするなんて変だなぁ」と思うかもしれません。しかし、配列が「一列に並んだお部屋」であることを思い出してください。 1番目のお部屋の住所がわかれば、そこに「1」を足すことで、隣にある2番目のお部屋にアクセスできるのです。
この「1を足す」という動作は、単純に数字を足すのとは少し違います。C++では、そのデータの型(int型なら4バイトなど)に合わせて、自動的に次の箱の位置までスキップしてくれます。これがポインタ演算の便利なところです。
3. ポインタを使って配列の中身を覗いてみよう
それでは、実際にポインタを使って配列の各要素にアクセスするプログラムを書いてみましょう。 配列名が先頭の住所を指していることを利用して、ポインタに代入してみます。
#include <iostream>
int main() {
// 3つの整数を持つ配列を作成
int my_array[3] = {10, 20, 30};
// 配列の先頭の住所をポインタに覚えさせる
int* ptr = my_array;
// ポインタを使って中身を表示
std::cout << "1番目の要素: " << *ptr << std::endl;
// ポインタを1つ進める(次の箱の住所にする)
ptr++;
std::cout << "2番目の要素: " << *ptr << std::endl;
return 0;
}
実行結果は以下のようになります。
1番目の要素: 10
2番目の要素: 20
コードの中の ptr++ という部分がポインタ演算です。
これにより、住所が「次の箱」へ移動し、アスタリスク * を使ってその中身(20)を取り出すことができました。
4. 配列の添え字 [ ] の正体
私たちが普段、配列を使うときによく見る my_array[1] という書き方。
実は、これこそがポインタ演算を使いやすくした「書き換え」にすぎないのです。
コンピュータの内部では、my_array[i] は *(my_array + i) という計算として処理されています。
つまり、「配列の先頭の住所から i 個分だけ進んだ場所の中身を見る」という意味なのです。
対応表:
my_array[0]は*(my_array + 0)と同じmy_array[1]は*(my_array + 1)と同じmy_array[2]は*(my_array + 2)と同じ
5. ポインタを操作して配列をループで回す
次に、少し応用編です。ループ処理(繰り返し)を使って、ポインタをどんどん進めながら配列のすべての値を表示してみましょう。 ポインタが配列の「看板」を持って、一歩ずつ隣の箱へ歩いていくイメージです。
#include <iostream>
int main() {
int scores[5] = {85, 92, 78, 64, 100};
int* p = scores; // 配列の先頭を指す
std::cout << "テストの点数を順番に表示します:" << std::endl;
for (int i = 0; i < 5; i++) {
// 現在の住所にある中身を表示して、次の住所へ移動する
std::cout << i + 1 << "人目: " << *(p + i) << "点" << std::endl;
}
return 0;
}
実行結果:
テストの点数を順番に表示します:
1人目: 85点
2人目: 92点
3人目: 78点
4人目: 64点
5人目: 100点
このように、*(p + i) という書き方を使うことで、配列の添え字を使わずにデータを取り出すことができました。
これができると、大量のデータを高速に処理するプログラムを書くときに非常に役立ちます。
6. 参照(リファレンス)との違いを知ろう
「ポインタ」と似た言葉に「参照(リファレンス)」があります。 初心者のうちはこの2つの違いで混乱しがちですが、決定的な違いは「移動できるかどうか」です。
ポインタは、先ほどの ptr++ のように、指し示す住所を後から自由に変更できます。
一方、参照は一度決めたら別の場所を指し示すことができない「あだ名」のようなものです。
配列のように、次々と隣の要素を見ていく必要がある場合は、ポインタの方が得意分野となります。
7. なぜポインタ演算が必要なの?
最近のプログラミング言語(PythonやJavaScriptなど)では、ポインタを直接触ることはほとんどありません。 では、なぜC++ではポインタ演算を学ぶ必要があるのでしょうか?
それは、「コンピュータの仕組みを最大限に活用するため」です。 メモリの住所を直接指定して操作することは、非常に効率が良く、ゲーム開発やロボットの制御、OS(WindowsやMacの基礎)といった、スピードが要求される分野では必須の知識となります。
また、配列とポインタの関係を理解しておくと、将来「動的メモリ確保(実行中に箱を増やす技術)」などの高度なトピックを学ぶときに、スムーズに理解できるようになります。
8. ポインタ演算の注意点(安全に使うために)
ポインタ演算は強力ですが、注意点もあります。 例えば、3つしか箱がない配列に対して、ポインタを10回も進めてしまったらどうなるでしょうか?
プログラムは「配列の外側」という、本来触ってはいけないメモリ領域を覗こうとしてしまいます。 これは、他人の家のクローゼットを勝手に開けるようなもので、プログラムが強制終了(クラッシュ)する原因になります。 ポインタを動かすときは、必ず「配列のサイズ内に収まっているか」を意識するようにしましょう。
9. 文字列も実はポインタと配列の関係
最後にもう一つ、面白い例を紹介します。C++で文字の集まり(文字列)を扱う際、char* str = "Hello"; と書くことがあります。
これも実はポインタ演算が使われています。
#include <iostream>
int main() {
const char* greeting = "Hello";
// 1文字目
std::cout << "1文字目: " << *greeting << std::endl;
// 2文字目(ポインタを1つ進める)
std::cout << "2文字目: " << *(greeting + 1) << std::endl;
return 0;
}
実行結果:
1文字目: H
2文字目: e
「文字列は文字(char)が並んだ配列」であり、その先頭の住所をポインタが持っている……という仕組みが見えてきましたね! このように、一見難しそうなポインタも、実はデータの並びを整理するための非常に合理的な道具なのです。