C++の多次元配列とは、配列の中にさらに配列を持つ構造のことです。
特に代表的なのが、2次元配列です。
2次元配列は、表、行列、座席表、ゲームの盤面、地図、画像のピクセル情報などを扱うときによく使われます。
たとえば、縦方向に「行」、横方向に「列」がある表のようなデータを表現したい場合に、2次元配列を使います。
C++では、多次元配列は見た目としては表のように扱えますが、内部的には「配列の配列」として扱われます。
つまり、2次元配列は「ある型の値が並んだ配列」が、さらに複数並んでいる構造です。
2次元配列の基本
2次元配列は「行」と「列」で考える
2次元配列は、一般的に「行」と「列」で考えると理解しやすくなります。
たとえば、2行3列のデータであれば、縦に2つ、横に3つの値を持つ表のようなイメージです。
C++では、それぞれの要素にアクセスするときに、行番号と列番号を指定します。
ただし、C++の配列は添字が0から始まります。
そのため、2行3列の配列であれば、行番号は0と1、列番号は0、1、2になります。
「2行3列」だからといって、行番号に2、列番号に3を指定できるわけではありません。
この点は初心者が特に間違えやすい部分です。
添字の範囲に注意する
C++の配列では、指定できる添字の範囲を超えてアクセスしてはいけません。
たとえば、3行4列の配列であれば、行番号は0から2まで、列番号は0から3までです。
この範囲を超えた場所にアクセスすると、範囲外アクセスになります。
C++では、範囲外アクセスをしても必ずしも分かりやすいエラーが出るとは限りません。
プログラムが一見動いてしまうこともありますが、これは非常に危険です。
範囲外アクセスは、未定義動作につながります。
未定義動作とは、プログラムがどのように動くか保証されない状態のことです。
値が壊れたり、別の変数に影響したり、突然クラッシュしたりする可能性があります。
そのため、多次元配列を扱うときは、行数と列数を正しく把握することが重要です。
多次元配列の初期化
初期化とは
初期化とは、配列を作ると同時に値を入れておくことです。
2次元配列では、行ごとに値をまとめて指定する形で初期化できます。
たとえば、2行3列の配列であれば、1行目の値、2行目の値をそれぞれまとめて書くイメージです。
このように初期化しておくと、あとから1つずつ値を代入しなくても、配列の中身をあらかじめ決めることができます。
最初の次元だけ省略できる
C++では、初期化するときに限り、最初の次元だけ省略できる場合があります。
たとえば、列数が決まっていて、初期化する値のまとまりから行数が判断できる場合、C++は行数を自動的に推測できます。
一方で、列数まで省略することはできません。
なぜなら、C++が多次元配列の要素位置を計算するためには、1行あたりにいくつの要素があるのかを知る必要があるからです。
つまり、2次元配列では「列数」がとても重要です。
行数は初期化内容から推測できる場合がありますが、列数が分からないと、各要素の位置を正しく決めることができません。
多次元配列のメモリ配置
見た目は表でも、メモリ上では連続している
C++の2次元配列は、見た目としては表のように扱えます。
しかし、メモリ上では1列に連続して並んでいます。
たとえば、2行3列の配列がある場合、メモリ上では1行目の値が左から順に並び、そのあとに2行目の値が続きます。
このように、行ごとに順番に並ぶ配置を、行優先順序と考えることができます。
C++の多次元配列は「配列の配列」なので、外側の配列の各要素が順番に並び、その各要素の中に内側の配列があるという構造です。
結果として、2次元配列全体の要素は、行ごとに連続してメモリ上に配置されます。
要素の位置は列数を使って決まる
2次元配列の要素の位置は、行番号と列番号から計算できます。
たとえば、1行あたり3個の要素がある場合、ある行のある列にアクセスするには、「その行より前に何個の要素があるか」と「その行の中で何番目か」を考えます。
このとき重要になるのが列数です。
1行あたりの要素数が分からないと、次の行がメモリ上のどこから始まるのか分かりません。
そのため、C++では多次元配列を扱うときに、最初以外の次元、特に2次元配列でいう列数が重要になります。
3次元配列とは
3次元配列の考え方
C++では、2次元配列だけでなく、3次元配列も扱えます。
3次元配列は、2次元配列がさらに複数まとまったものと考えると理解しやすいです。
たとえば、2次元配列を「表」と考えるなら、3次元配列は「表が複数枚ある」ようなイメージです。
よくある説明では、「ブロック」「行」「列」のように表現されることがあります。
ただし、これはあくまで理解しやすくするための表現です。
C++の型として厳密に見ると、3次元配列も「配列の配列の配列」です。
3次元配列も添字は0から始まる
3次元配列でも、各次元の添字は0から始まります。
たとえば、ブロック、行、列の3つの位置を指定するとき、それぞれの番号は0始まりになります。
2次元配列と同じく、範囲外の添字を指定すると未定義動作につながるため注意が必要です。
関数に2次元配列を渡すときの注意点
列数は指定する必要がある
C++で2次元配列を関数に渡す場合、列数は指定する必要があります。
これは、関数の中で配列の要素位置を計算するためです。
関数側で「1行あたり何個の要素があるのか」が分からないと、指定された行と列の要素がメモリ上のどこにあるのか判断できません。
そのため、2次元配列を関数に渡す場合は、行数よりも列数が重要です。
関数引数では配列がポインタに調整される
C++では、関数の引数として配列を受け取るとき、配列そのものが丸ごとコピーされるわけではありません。
多くの場合、配列はポインタとして扱われる形に調整されます。
2次元配列の場合、関数の引数として受け取ると、実質的には「一定の列数を持つ配列へのポインタ」として扱われます。
そのため、関数の引数に行数を書いたとしても、最初の次元の数は型の本質的な制約としてはあまり意味を持ちません。
一方で、列数は必要です。
この点は、C++の多次元配列で特に混乱しやすい部分です。
行数は別途渡すことが多い
関数に2次元配列を渡す場合、列数は型として必要になりますが、行数は別途引数として渡すことがよくあります。
なぜなら、関数側では「何行分処理すればよいのか」を知る必要があるからです。
つまり、関数に2次元配列を渡すときは、次のように考えると分かりやすいです。
列数は型として必要。
行数は処理範囲として必要。
この2つは役割が異なります。
固定長配列と可変長のデータ構造
固定長配列の特徴
C++の通常の配列は、基本的にサイズがコンパイル時に決まっている必要があります。
たとえば、「2行3列」のように、あらかじめサイズが決まっている場合には固定長配列を使えます。
固定長配列のメリットは、シンプルで高速に扱いやすいことです。
また、メモリ上で全体が連続して配置されるため、データの並びが分かりやすいという特徴もあります。
一方で、プログラムの実行中に行数や列数を自由に変えることはできません。
実行時にサイズが決まる場合は注意が必要
C++では、標準の組み込み配列において、実行時に入力された値をそのまま配列サイズとして使うことはできません。
一部のコンパイラでは拡張機能として動く場合がありますが、それは標準C++の機能ではありません。
そのため、ユーザー入力やファイル読み込みなどで行数・列数が実行時に決まる場合は、標準ライブラリのコンテナを使うのが一般的です。
代表的なのが、可変長の配列として使える vector です。
vectorを使った多次元データ
vectorを使うメリット
C++では、サイズが実行時に決まる表形式のデータを扱うときに、vector がよく使われます。
vector を使えば、行数や列数を入力値に応じて決めることができます。
たとえば、競技プログラミングや実務のデータ処理では、あらかじめ配列サイズが固定されていないことがよくあります。
そのような場合、固定長配列よりも vector の方が扱いやすいです。
vectorの中にvectorを入れる方法
2次元データを表す代表的な方法として、vector の中にさらに vector を入れる方法があります。
これは「行の集まり」として考えると分かりやすいです。
外側の vector が行を表し、内側の vector が各行の列を表します。
この方法は直感的で、初心者にも理解しやすい書き方です。
vectorのメモリ配置には注意する
vector の中に vector を入れる方法は便利ですが、メモリ配置には注意が必要です。
各行の中の要素は連続して配置されます。
しかし、2次元データ全体が1つの大きな連続したメモリ領域に配置されるとは限りません。
なぜなら、各行が別々の vector として内部データを持つためです。
つまり、1行目の要素は連続していて、2行目の要素も連続しています。
しかし、1行目の最後の要素のすぐ後ろに、必ず2行目の最初の要素があるとは限りません。
通常の学習や一般的な処理では大きな問題になりにくいですが、大量のデータを高速に処理したい場合には重要なポイントになります。
1次元vectorで2次元データを扱う方法
全体を連続したメモリにしたい場合
2次元データ全体を1つの連続したメモリ領域として扱いたい場合は、1次元の vector を使って2次元のように扱う方法があります。
この方法では、本来なら「行」と「列」で考えるデータを、1本の長い配列として持ちます。
そして、行番号と列番号から、1次元上の位置を計算してアクセスします。
この考え方は、C++の固定長2次元配列のメモリ配置に近いです。
高速処理で有利になる場合がある
1次元の vector で2次元データを扱う方法は、メモリが連続しているため、キャッシュ効率の面で有利になる場合があります。
特に、大きな行列、画像データ、数値計算、大量のグリッド処理などでは、この方法が使われることがあります。
ただし、初心者にとっては、行番号と列番号から位置を計算する必要があるため、少し分かりにくく感じるかもしれません。
最初は、直感的に扱いやすい vector の中に vector を入れる方法から学び、必要に応じて1次元 vector の方法を理解するとよいです。
std::arrayを使う方法
std::arrayとは
std::array は、C++標準ライブラリに用意されている固定サイズの配列コンテナです。
通常の配列と同じように固定サイズですが、標準ライブラリのコンテナとして扱えるため、よりC++らしい書き方ができます。
多次元データを扱う場合は、std::array の中にさらに std::array を入れる形で表現できます。
std::arrayのメリット
std::array のメリットは、固定サイズでありながら、通常の配列よりも扱いやすい点です。
通常の配列は、関数に渡すときにポインタとして扱われやすく、サイズ情報を失いやすいです。
一方で、std::array は型の中にサイズ情報を持っています。
そのため、関数に渡してもサイズが分かりやすく、安全に扱いやすいという利点があります。
ただし、サイズはコンパイル時に決まっている必要があります。
実行時にサイズが変わるデータを扱いたい場合は、vector の方が適しています。
文字の2次元データを扱う場合
盤面や迷路では文字列の配列が便利
迷路、ゲームの盤面、地図のような文字データを扱う場合は、文字の2次元配列を使うことがあります。
ただし、C++ではこのようなケースで、文字の配列を直接使うよりも、文字列を複数持つ形の方が扱いやすいことが多いです。
たとえば、1行を1つの文字列として持ち、それを複数行分並べるイメージです。
この方法であれば、各マスの文字に行番号と列番号でアクセスできます。
vectorがよく使われる
競技プログラミングなどでは、文字の盤面を扱うときに vector<string> がよく使われます。
これは、各行を1つの文字列として管理する方法です。
たとえば、壁を表す記号、空白マスを表す記号、スタート地点やゴール地点を表す記号などを、行と列で扱えます。
vector<vector<char>> のような形でも表現できますが、文字列の集合として扱う方が、入力や表示が簡潔になることが多いです。
多次元配列でよくあるミス
添字の範囲を超える
最もよくあるミスは、添字の範囲を超えてアクセスすることです。
2行3列の配列であれば、使える行番号は0と1、列番号は0、1、2です。
それを超える行番号や列番号を指定してはいけません。
範囲外アクセスは未定義動作につながるため、プログラムが壊れる原因になります。
初期化していない値を使う
関数内で作った通常の配列は、明示的に初期化しないと中身が不定値になることがあります。
不定値とは、どんな値が入っているか分からない状態です。
その状態で値を読み取ると、予想外の結果になる可能性があります。
配列を作ったあとに値を入れる予定がある場合でも、すべての要素に確実に値を入れてから使うことが大切です。
関数に渡すときに列数を省略する
2次元配列を関数に渡すとき、列数を省略しようとしてエラーになることがあります。
C++では、関数内で要素の位置を計算するために、1行あたりの要素数が必要です。
そのため、2次元配列を関数に渡す場合、列数を指定しなければなりません。
実行時に決まるサイズで固定長配列を作ろうとする
標準C++では、通常の固定長配列のサイズはコンパイル時に分かる必要があります。
そのため、ユーザーが入力した行数や列数をそのまま使って通常の配列を作ることはできません。
このような場合は、vector などの標準コンテナを使うのが適切です。
多次元配列の使い分け
サイズが固定なら通常の配列やstd::array
サイズがあらかじめ決まっていて、小さめのデータを扱う場合は、通常の配列や std::array が使えます。
通常の配列はシンプルで、メモリ上でも連続して配置されます。
std::array は、固定サイズでありながら標準ライブラリのコンテナとして扱えるため、C++らしく安全に書きたい場合に向いています。
サイズが実行時に決まるならvector
行数や列数が実行時に決まる場合は、vector を使うのが基本です。
入力データの大きさが変わる場合や、ファイルから読み込むデータの件数が不明な場合などに適しています。
特に、競技プログラミングや一般的なアプリケーション開発では、vector を使う場面が多いです。
文字の盤面ならvector
迷路やゲーム盤面のように、文字で構成された2次元データを扱う場合は、vector<string> が便利です。
各行を文字列として読み込めるため、入力処理が簡潔になります。
また、行番号と列番号を使って各文字にアクセスできるので、2次元配列のような感覚で扱えます。
高速処理を重視するなら1次元vector
大量の数値データや画像データ、行列計算などで高速処理を重視する場合は、1次元 vector を使って2次元データを表す方法が有効です。
この方法では、2次元全体を1つの連続したメモリ領域として扱えるため、メモリアクセスの効率が良くなる場合があります。
ただし、行と列から位置を計算する必要があるため、コードの分かりやすさは少し下がります。
学習初期は vector の中に vector を入れる方法で十分です。
パフォーマンスが必要になった段階で、1次元 vector の方法を学ぶとよいでしょう。
C++の多次元配列で特に重要なポイント
多次元配列は配列の配列である
C++の多次元配列は、表のように見えますが、実際には配列の中に配列が入っている構造です。
この考え方を理解しておくと、関数に渡すときやメモリ配置を考えるときに理解しやすくなります。
添字は0から始まる
C++の配列は、すべて0番目から始まります。
2行3列であれば、行番号は0と1、列番号は0、1、2です。
行数や列数と、最後に使える添字を混同しないように注意が必要です。
列数は非常に重要
2次元配列では、列数が分からないと各要素の位置を計算できません。
そのため、関数に2次元配列を渡すときにも列数が必要になります。
「行数よりも列数が重要になる場面がある」という点は、C++の多次元配列を理解するうえで大切です。
vectorと固定長配列はメモリ構造が異なる
通常の2次元配列は、全体が連続したメモリ領域に配置されます。
一方で、vector の中に vector を入れた形では、各行の中身は連続していますが、2次元全体が連続しているとは限りません。
この違いは、基本的なプログラムではあまり問題になりませんが、高速処理や大規模データ処理では重要になることがあります。
まとめ
C++の多次元配列は、複数の添字を使ってデータを管理する仕組みです。
特によく使われるのは2次元配列で、表、行列、盤面、地図、画像などのデータを扱うときに便利です。
C++の多次元配列は、内部的には「配列の配列」として扱われます。
2次元配列の場合、行と列を指定して要素にアクセスします。
基本的な考え方としては、次の点を押さえておくとよいです。
- 多次元配列は配列の配列である。
- 添字は0から始まる。
- 範囲外アクセスは未定義動作になる。
- 2次元配列では列数が重要である。
- 固定長配列は全体が連続したメモリに配置される。
- 実行時にサイズが決まる場合は
vectorを使う。 - 文字の盤面では
vector<string>が便利。 - 高速処理では1次元
vectorで2次元データを表す方法もある。
初心者のうちは、まず「行」と「列」で2次元配列を理解し、二重ループで処理する考え方を身につけることが大切です。
そのうえで、固定長配列、std::array、vector、1次元 vector を使い分けられるようになると、C++で多次元データを柔軟に扱えるようになります。
以上、C++の多次元配列についてでした。
最後までお読みいただき、ありがとうございました。
