C++では、配列とポインタは非常によく似た形で使われます。
そのため、学び始めた段階では「配列はポインタなのか」「配列名はなぜポインタのように振る舞うのか」で混乱しやすくなります。
最初に結論を言うと、配列とポインタは別物です。
ただし、配列は多くの式の中で先頭要素へのポインタに変換されるため、結果としてポインタのように見える場面が多くあります。
この「別物だが、よく似た振る舞いをする」という点を正確に理解することが、配列のポインタを理解するうえでの土台になります。
配列とポインタは同じではない
配列は、同じ型の要素が連続して並んだまとまりです。
たとえば「5個の整数を持つ配列」であれば、その配列は5個分の整数全体をひとまとまりとして表しています。
一方、ポインタは値そのものではなく、どこにその値があるかを示すアドレスを保持するものです。
つまり、配列はデータそのものであり、ポインタはそのデータの場所を指し示すためのものです。
この違いは非常に重要です。
配列は配列であり、ポインタはポインタです。
たまたま似た書き方や似た操作ができるからといって、同じものだと考えてしまうと、二次元配列や関数引数のところで必ず混乱します。
なぜ配列がポインタのように見えるのか
C++では、配列は多くの式の中で先頭要素へのポインタに変換されるという性質があります。
これが、配列がポインタのように見える理由です。
たとえば、配列名に対して添字を使ったり、間接参照のような操作が成立したり、先頭から順に要素へアクセスできたりするのは、配列そのものがポインタだからではありません。
配列がその場で先頭要素を指すポインタに変換されているからです。
ここで大切なのは、「配列名はポインタそのもの」と覚えないことです。
正確には、配列は多くの場面で先頭要素へのポインタとして扱われるのであって、常にポインタになっているわけではありません。
配列が常にポインタになるわけではない
配列について説明するときによく出てくるのが、「配列名はポインタになる」という表現です。
これは入門段階では便利ですが、厳密には不十分です。
正確には、配列は多くの式で先頭要素へのポインタに変換されます。
しかし、すべての場面でそうなるわけではありません。
たとえば、配列全体の大きさを調べる場面や、配列そのもののアドレスを取得する場面では、配列はポインタに変換されません。
そのような場面では、配列はあくまで「配列そのもの」として扱われます。
この違いを理解していないと、「なぜある場所ではポインタのように使えて、別の場所では配列のままなのか」がわからなくなります。
C++では、配列は本来配列であり、必要な文脈においてだけポインタへ変換される、と考えるのが正確です。
配列の先頭要素へのポインタ
もっとも基本的なのは、配列の先頭要素を指すポインタです。
これは普通のポインタであり、配列全体を指しているわけではなく、あくまで最初の要素を指しているだけです。
このポインタを使うと、先頭要素から順番に次の要素へ移動できます。
その結果、配列の各要素へアクセスできます。
ここで重要なのは、ポインタが配列全体を覚えているわけではないということです。
ポインタが知っているのは「どこを指しているか」であり、配列の要素数や全体サイズの情報まで自動的に持っているわけではありません。
そのため、配列とポインタは似たように使えても、保持している情報の量は違います。
ポインタ演算とは何か
ポインタに対して加算するとき、1バイトずつ進むわけではありません。
指している型の1要素分だけ進むというのがポイントです。
整数を指しているポインタなら、整数1個分だけ先へ進みます。
そのため、先頭要素を指すポインタに1を足すと、次の要素を指すようになります。
これによって、配列の先頭を指すポインタから順番に全要素へたどることができます。
この仕組みが、配列とポインタが強く結びついて見える大きな理由のひとつです。
「要素へのポインタ」と「配列へのポインタ」は別物
「配列のポインタ」という言い方は曖昧で、実際には二つの異なるものが混同されやすくなっています。
一つは、配列の先頭要素を指すポインタです。
これは普通の要素型へのポインタであり、最初の要素を起点として各要素へアクセスするために使います。
もう一つは、配列そのものを指すポインタです。
これは配列全体をひとまとまりとして指しています。
この二つは意味がまったく違います。
前者は「要素を指すポインタ」、後者は「配列を指すポインタ」です。
どちらも見た目が似ているため混乱しやすいのですが、型もポインタ演算の意味も異なります。
配列そのものを指すポインタとは何か
配列そのものを指すポインタは、配列全体を一つのまとまりとして扱います。
このポインタを1つ進めると、要素1個分ではなく、配列1個分進みます。
ここが、先頭要素を指す普通のポインタとの決定的な違いです。
たとえば、要素へのポインタであれば1つ進めると次の要素へ進みます。
しかし、配列へのポインタであれば1つ進めたときに進む単位は「配列全体」です。
この違いは特に二次元配列を扱うときに非常に重要になります。
一次元配列だけを見ていると必要性がわかりにくいのですが、二次元配列ではこの考え方が本質になります。
配列名と配列全体のアドレスは違う
配列について学ぶとき、非常に重要なのが「配列名」と「配列全体のアドレス」の違いです。
配列名は、多くの式の中では先頭要素へのポインタに変換されます。
一方で、配列全体のアドレスを取るときは、配列そのものへのポインタになります。
この二つは、見た目のアドレス値が同じ場所を表しているように見えることがあります。
なぜなら、どちらも配列の先頭位置を起点にしているからです。
しかし、型が違うため、意味は同じではありません。
そして、型が違うということは、そこに対して行うポインタ演算の意味も変わるということです。
つまり、同じ位置を表しているように見えても、
- 先頭要素を指しているのか
- 配列全体を指しているのか
で、扱い方はまったく異なります。
宣言が難しく見える理由
C++では、配列へのポインタとポインタの配列が非常によく似た見た目になります。
そのため、記法に慣れていない段階では読み違えやすくなります。
重要なのは、「変数そのものがポインタなのか」「変数そのものが配列なのか」を見分けることです。
見た目が少し違うだけで意味は大きく変わります。
一方は「配列を指すポインタ」であり、もう一方は「ポインタを要素として持つ配列」です。
この違いを理解していないと、二次元配列の話に進んだときに一気にわからなくなります。
二次元配列で本質がはっきり見える
二次元配列は、「配列の配列」です。
たとえば2行3列の配列であれば、全体としては「3個の要素を持つ配列が2つ並んでいる」と考えられます。
ここで重要なのは、二次元配列の先頭要素は整数そのものではないという点です。
先頭要素は「1行分の配列」です。
したがって、二次元配列が多くの式の中でポインタに変換されるとき、その変換先は「整数へのポインタ」ではありません。
1行分の配列を指すポインタになります。
これが、二次元配列が一次元配列より難しく感じられる最大の理由です。
一次元配列では先頭要素が単純な値なので直感的ですが、二次元配列では先頭要素そのものが配列になっているため、型が一段複雑になります。
二次元配列を普通の要素ポインタでは受けられない理由
二次元配列は、見た目には数値が並んでいるので、単純な要素ポインタで扱えそうに見えることがあります。
しかし、実際にはそうではありません。
理由は、二次元配列がポインタに変換されるときに得られるのが、整数へのポインタではなく、1行分の配列を指すポインタだからです。
つまり、二次元配列の先頭は「整数」ではなく「行配列」です。
そのため、一次元配列と同じ感覚で「整数へのポインタ」で受けようとすると型が一致しません。
この点を理解すると、二次元配列では「列数が必要になる」ことや、「行単位で進む」という挙動も自然に理解しやすくなります。
二次元配列とポインタへのポインタは別物
二次元配列を学ぶとき、非常によくある誤解が「二次元配列はポインタへのポインタで表せるのではないか」というものです。
しかし、通常の二次元配列と、ポインタへのポインタは別物です。
二次元配列は、連続したメモリ上に行単位で整然と並んだ「配列の配列」です。
一方、ポインタへのポインタは、「ポインタを指すポインタ」です。
これは、各行を別々の場所に持つ構造にもできるため、必ずしも全体が連続したメモリになるとは限りません。
見た目の使い方が似ていても、内部構造と型が違います。
そのため、普通の二次元配列をそのままポインタへのポインタとして扱うことはできません。
関数引数で配列の見え方が変わる理由
C++では、関数の引数に配列を書くことができます。
ただし、このとき非常に重要なのは、関数パラメータとして書かれた配列型は、そのまま配列として保持されるわけではないということです。
関数引数における配列は、実質的にはポインタとして扱われます。
そのため、関数の中では「配列そのもの」ではなく、「先頭要素を指すポインタ」として見えることになります。
このため、関数の中では配列全体の要素数や全体サイズはそのままではわかりません。
呼び出し元にあった「配列としての大きさの情報」は、その形のまま保持されないからです。
この仕組みを知らないと、「なぜ関数の中では配列サイズが取れないのか」がわかりにくくなります。
配列サイズを保持したいなら参照で受ける
配列を関数に渡すとき、単に先頭要素へのポインタとして受けるのではなく、配列そのものへの参照として受ける方法があります。
この方法を使うと、配列がポインタとして扱われるのではなく、元の配列型の情報を保ったまま受け取れます。
そのため、要素数も型情報の一部として維持できます。
これは、配列サイズをコンパイル時に知りたいときに非常に有効です。
配列とポインタの違いがよく表れる部分でもあります。
二次元配列を関数に渡すときに列数が必要な理由
二次元配列を関数に渡すとき、列数を明示する必要があることがあります。
これは不自然に見えるかもしれませんが、理由は明確です。
二次元配列がポインタに変換されたとき、関数側では「1行分の配列を指すポインタ」として扱います。
このとき、次の行へ進むには「1行分が何個の要素から成るか」を知っていなければなりません。
つまり、行単位でポインタ演算を行うために、列数が必要になるのです。
列数がわからなければ、次の行の開始位置を正しく計算できません。
よくある誤解
ここまでの内容を踏まえると、よくある誤解は次のように整理できます。
まず、配列名はポインタそのものではありません。
配列は配列であり、多くの式でポインタに変換されるだけです。
次に、配列名と配列全体のアドレスは同じではありません。
見た目の位置が同じに見えても、型が違い、意味も異なります。
さらに、通常の二次元配列はポインタへのポインタではありません。
二次元配列は配列の配列であり、ポインタへのポインタとは内部構造も型も違います。
この三つをきちんと区別できるようになると、配列とポインタの関係がかなり整理されます。
最後に押さえるべき要点
C++の配列のポインタで本当に重要なのは、次の理解です。
- 配列とポインタは別物である
- 配列は多くの式で先頭要素へのポインタに変換される
- ただし、すべての場面でそうなるわけではない
- 要素へのポインタと、配列そのものへのポインタは別物である
- 二次元配列では、先頭要素が「1行分の配列」になる
- そのため、二次元配列は単純な要素ポインタやポインタへのポインタとは一致しない
- 関数引数では配列はポインタとして扱われるため、配列サイズの情報はそのままでは残らない
まとめ
C++で配列のポインタが難しく感じる理由は、配列とポインタが似て見えるのに、実際には別物だからです。
しかも、配列は多くの場面でポインタへ変換されるため、見た目の上ではますます区別がつきにくくなります。
しかし、本質は比較的はっきりしています。
配列はデータのまとまりそのものであり、ポインタはその場所を指し示すものです。
配列の先頭要素を指すポインタと、配列全体を指すポインタもまた別物です。
そして二次元配列では、その違いがよりはっきり表れます。
この構造を一つずつ整理して理解すると、C++の配列とポインタの関係はかなり明快になります。
以上、C++の配列のポインタについてでした。
最後までお読みいただき、ありがとうございました。
