C++の二次元配列とは、データを「行」と「列」の形で管理できる配列のことです。
表やマス目、盤面、行列、迷路のように、縦横のあるデータを扱うときによく使われます。
たとえば、学校の成績表、ゲームの盤面、画像のピクセル情報、座席表のようなデータは、二次元配列で表現しやすいです。
C++では、二次元配列を「表のようなもの」として考えると理解しやすくなります。
ただし、厳密には二次元配列は「配列の中に配列が入っている構造」です。
つまり、C++の二次元配列は「配列の配列」として扱われます。
初心者のうちは、まず「行と列でデータを管理する配列」と考えるとよいでしょう。
C++で二次元配列を宣言する基本
二次元配列は行数と列数を指定して宣言する
C++で二次元配列を宣言するときは、保存するデータの型、配列名、行数、列数を指定します。
たとえば、整数を保存する二次元配列を作る場合は、「何行あるのか」「何列あるのか」を決めて宣言します。
二次元配列では、最初の指定が行数、次の指定が列数を表します。
つまり、「縦に何個のまとまりがあるか」「横に何個の要素があるか」を決めるイメージです。
3行4列の二次元配列であれば、全部で12個の値を保存できます。
これは、3つの行があり、それぞれの行に4つの要素があるという意味です。
添字は0から始まる
C++の配列では、要素の番号である添字は0から始まります。
そのため、3行4列の二次元配列を作った場合、行番号は1から3ではなく、0から2になります。
列番号も1から4ではなく、0から3になります。
この点は初心者が間違えやすい部分です。
「3行あるから最後の行番号は3」と考えてしまうと、範囲外アクセスになります。
C++では、配列の範囲外にアクセスしても必ずエラーが表示されるとは限りません。
場合によってはプログラムが動いているように見えても、予期しない動作をすることがあります。
より正確にいうと、配列の範囲外アクセスは未定義動作です。
未定義動作とは、プログラムがどのように動くか保証されない状態のことです。
二次元配列の初期化
宣言だけでは値が入っているとは限らない
C++で二次元配列を宣言しただけでは、中身に安全な値が入っているとは限りません。
特に、関数の中で通常のローカル変数として二次元配列を宣言した場合、各要素には不定値が入る可能性があります。
不定値とは、何が入っているかわからない値のことです。
そのため、二次元配列を使うときは、できるだけ初期化してから使うのが安全です。
ただし、すべての場合で未初期化になるわけではありません。
グローバル変数として宣言した配列や、staticを付けた配列は、自動的に0で初期化されます。
つまり、初期化の扱いは宣言する場所によって変わります。
0で初期化すると安全に使いやすい
二次元配列を使うときは、最初にすべての要素を0で初期化しておくと安全です。
特に、数値計算、カウント処理、表の集計、盤面管理などでは、初期値が0であることを前提に処理を書くことが多くあります。
未初期化のまま使うと、意図しない値が計算に混ざる可能性があります。
そのため、初心者のうちは「配列は宣言したら初期化する」と覚えておくとよいでしょう。
値を指定して初期化することもできる
二次元配列は、宣言と同時に各要素の値を指定して初期化することもできます。
たとえば、3行4列の表にあらかじめ値を入れておきたい場合、行ごとに値を並べて初期化できます。
この方法を使うと、プログラムの中で初期状態の表や盤面をわかりやすく表現できます。
ゲームの初期配置、固定の行列データ、テスト用のデータなどを用意するときに便利です。
二次元配列の使い方
要素には行と列を指定してアクセスする
二次元配列の各要素にアクセスするときは、行番号と列番号を指定します。
最初の番号で行を指定し、次の番号で列を指定します。
たとえば、先頭の要素にアクセスする場合は、行番号も列番号も0になります。
2行目の3列目にアクセスする場合は、行番号は1、列番号は2になります。
このように、実際の「1行目」「2行目」という感覚と、C++の添字の番号には1つずれがあります。
初心者のうちは、次のように考えるとわかりやすいです。
実際の1行目は、C++では行番号0です。
実際の2行目は、C++では行番号1です。
実際の3行目は、C++では行番号2です。
列についても同じです。
二重ループで処理することが多い
二次元配列は、行と列を持つデータなので、処理するときはループを2つ使うことが多いです。
外側のループで行を順番に処理し、内側のループで列を順番に処理します。
この考え方は、二次元配列を使ううえで非常に重要です。
たとえば、すべての要素を表示したい場合、各行を順番に見て、その行の中の各列を順番に見ていきます。
合計値を求める場合も同じで、すべての行と列を順番に確認しながら値を加算します。
二次元配列では、「行を動かす処理」と「列を動かす処理」を分けて考えると理解しやすくなります。
二次元配列の宣言で注意すべき点
列数は基本的に省略できない
C++の二次元配列では、初期化の内容から行数がわかる場合、行数を省略できることがあります。
しかし、列数は基本的に省略できません。
これは、C++が二次元配列の要素の位置を計算するために、1行あたりの要素数を知る必要があるからです。
たとえば、ある要素にアクセスするとき、C++は「1行に何個の要素があるか」をもとに、その要素の場所を計算します。
列数がわからないと、次の行がどこから始まるのか判断できません。
そのため、二次元配列を宣言するときは、列数を明確に指定する必要があります。
関数に渡すときも列数が重要
二次元配列を関数に渡す場合も、列数の指定が重要です。
関数側では、受け取った二次元配列の1行あたりの要素数を知っていなければ、正しく要素にアクセスできません。
一方で、関数の引数として書く場合、行数はあまり重要ではありません。
関数に渡された配列は、内部的にはポインタのように扱われるためです。
そのため、関数に二次元配列を渡す場合は、「行数よりも列数が重要」と覚えておくとよいでしょう。
固定サイズの二次元配列と可変サイズの二次元配列
固定サイズの二次元配列
通常の二次元配列は、サイズがあらかじめ決まっている場合に向いています。
たとえば、8×8の盤面、3×3のマス目、固定サイズの行列などです。
サイズが最初から決まっている場合は、通常の二次元配列を使うことでシンプルに表現できます。
ただし、通常の配列では、サイズは基本的にコンパイル時に決まっている必要があります。
つまり、プログラムの実行中に入力された値をそのまま配列のサイズとして使うことは、標準C++ではできません。
一部のコンパイラでは動くことがありますが、それは標準C++の機能ではない場合があります。
そのため、実行時にサイズが決まる場合は、別の方法を使うのが安全です。
実行時にサイズが決まる場合はvectorを使う
行数や列数が実行時に決まる場合は、vectorを使うのが一般的です。
vectorはC++でよく使われる動的配列で、プログラムの実行中にサイズを決めやすいという特徴があります。
二次元配列のように使いたい場合は、vectorの中にvectorを入れる形にします。
この形にすると、行と列を持つデータ構造として扱えます。
競技プログラミングや実務のコードでは、入力によって行数や列数が変わる場面が多いため、通常の二次元配列よりもvectorを使うことがよくあります。
ただし、厳密にはvectorを使ったものは通常の二次元配列そのものではありません。
正確には、「vectorの中にvectorが入っている構造」です。
とはいえ、行番号と列番号を指定して要素にアクセスできるため、実用上は二次元配列のように扱えます。
vectorを使う場合の注意点
通常の二次元配列とは内部構造が異なる
通常の二次元配列では、要素がメモリ上に連続して並びます。
一方、vectorの中にvectorを入れる形では、各行がそれぞれ別のvectorとして管理されます。
そのため、通常の二次元配列と完全に同じメモリ構造ではありません。
初心者のうちはあまり深く気にしなくても問題ありませんが、処理速度やメモリ効率が重要になる場面では、この違いが影響することがあります。
一般的な学習や小規模なプログラムでは、まずは扱いやすいvectorを使えるようになることが大切です。
初期値を指定できる
vectorを使う場合も、最初からすべての要素に同じ値を入れておくことができます。
たとえば、すべての要素を0にしたり、すべての要素を特定の値にしたりできます。
これは、迷路の未訪問状態、ゲーム盤の空きマス、計算前の初期値などを表現するときに便利です。
特に、初期値を明確にしておくことで、プログラムの意図がわかりやすくなります。
文字を扱う二次元配列
char型の二次元配列
二次元配列は整数だけでなく、文字を扱うこともできます。
文字を使った二次元配列は、盤面やマップを表すときによく使われます。
たとえば、丸やバツ、壁や道、空白や障害物などを文字で表すと、状態を視覚的に管理しやすくなります。
ゲームの盤面、迷路、地図、座席表などを作る場合に便利です。
文字列の配列として扱う方法もある
C++では、文字の二次元データを扱う方法として、文字列を複数並べる方法もよく使われます。
特に、迷路やマップのようなデータでは、1行を1つの文字列として表し、それを複数行分まとめて管理する方法が便利です。
この方法では、行番号と列番号を指定することで、特定の位置にある文字を取り出せます。
ただし、二次元の盤面として扱う場合は、各行の文字列の長さをそろえておくことが大切です。
行ごとの長さが違うと、長方形の表として扱いにくくなるためです。
std::arrayを使った二次元配列
固定サイズならstd::arrayも使える
C++では、通常の配列のほかにstd::arrayを使って固定サイズの配列を作ることもできます。
std::arrayは、通常の配列に近い性質を持ちながら、C++の標準ライブラリとして用意されている便利な型です。
二次元的に使いたい場合は、std::arrayの中にstd::arrayを入れる形にします。
ただし、書き方がやや複雑になるため、初心者のうちは通常の二次元配列やvectorを先に理解するとよいでしょう。
初心者は通常配列とvectorを優先して覚えるとよい
C++で二次元配列を学ぶ場合、最初に覚えるべきなのは通常の二次元配列とvectorです。
サイズが固定なら通常の二次元配列、サイズが実行時に変わるならvector、という使い分けを理解できれば、多くの場面に対応できます。
std::arrayは、固定サイズでありながら標準ライブラリの機能も使いたい場合に便利です。
ただし、最初から無理に使いこなす必要はありません。
二次元配列でよくある間違い
添字を1から始めてしまう
C++の配列は0から始まります。
そのため、3行4列の配列であれば、最後の行番号は3ではなく2です。
最後の列番号は4ではなく3です。
このずれを理解していないと、範囲外アクセスをしてしまいます。
範囲外アクセスは、プログラムの不具合の原因になります。
エラーとしてすぐに見つかるとは限らないため、特に注意が必要です。
初期化せずに使ってしまう
二次元配列を宣言しただけで、すぐに安全な値が入っていると考えるのは危険です。
関数内で通常のローカル配列として宣言した場合、初期化しなければ中身は不定値になります。
不定値を使って計算すると、結果が毎回変わったり、意図しない動作になったりする可能性があります。
そのため、二次元配列を使うときは、必要に応じて初期化する習慣をつけることが大切です。
実行時に決まるサイズを通常配列に使おうとする
C++では、通常の配列のサイズは基本的にコンパイル時に決まっている必要があります。
そのため、ユーザーの入力によって行数や列数が決まる場合、通常の二次元配列ではなくvectorを使うのが適切です。
一部の環境では、実行時に決まる値を配列サイズとして使えることがあります。
しかし、それは標準C++として保証された書き方ではない場合があります。
移植性や安全性を考えるなら、実行時にサイズが決まるデータにはvectorを使うとよいでしょう。
列数を省略してしまう
二次元配列では、列数の指定が重要です。
初期化の内容から行数を推測できる場合は行数を省略できることがありますが、列数は基本的に省略できません。
列数がないと、C++は各行がどこで区切られるのか判断できません。
そのため、二次元配列を宣言するときは、列数を忘れずに指定する必要があります。
通常の二次元配列とvectorの使い分け
サイズが固定なら通常の二次元配列
行数と列数が最初から決まっている場合は、通常の二次元配列が使いやすいです。
たとえば、固定された盤面、決まったサイズの表、小さな行列などには通常の二次元配列が向いています。
通常の二次元配列は、構造がシンプルで、固定サイズのデータを扱う場合に適しています。
サイズが変わるならvector
入力内容や処理の結果によって行数や列数が変わる場合は、vectorを使うのがおすすめです。
vectorを使えば、プログラムの実行中に決まるサイズにも対応しやすくなります。
特に、競技プログラミングでは、問題文で行数と列数が入力として与えられることが多いため、vectorを使った二次元データの管理がよく使われます。
実務でも、データの件数があらかじめ決まっていない場面ではvectorのほうが柔軟です。
C++の二次元配列を理解するポイント
「行」と「列」の考え方を身につける
二次元配列を理解するうえで大切なのは、「行」と「列」の考え方です。
どちらが行で、どちらが列なのかを意識しておくことで、データの取り出しや更新がしやすくなります。
一般的には、最初の添字が行、次の添字が列です。
表で考えると、まず上から何行目かを選び、次に左から何列目かを選ぶイメージです。
添字が0から始まることを忘れない
C++では、配列の添字は0から始まります。
これは二次元配列でも同じです。
実際の感覚では「1行目」「1列目」と呼ぶ場所も、C++では行番号0、列番号0になります。
このルールに慣れることが、配列を正しく扱うための第一歩です。
初期化を意識する
二次元配列を安全に使うためには、初期化を意識することが重要です。
特に、関数内で宣言した通常の配列は、初期化しなければ不定値を持つ可能性があります。
予期しない不具合を避けるためにも、必要な場合は最初に値を入れてから使うようにしましょう。
範囲外アクセスを避ける
二次元配列では、行数と列数の範囲を超えてアクセスしないように注意する必要があります。
範囲外アクセスは、コンパイル時に必ず検出されるわけではありません。
実行時にもすぐにエラーになるとは限らず、プログラムの別の部分に悪影響を与えることもあります。
安全に使うためには、ループの条件や添字の範囲を正しく設定することが大切です。
まとめ
C++の二次元配列は、行と列を持つデータを扱うための仕組みです。
表、行列、盤面、迷路、座席表など、縦横のあるデータを表現する場面でよく使われます。
通常の二次元配列では、行数と列数を指定して宣言します。
C++の配列は0から始まるため、添字の範囲には注意が必要です。
また、関数内で宣言した通常の配列は、初期化しないと不定値を持つことがあります。
安全に使うためには、最初に初期化しておくことが大切です。
二次元配列を関数に渡す場合は、列数の指定が重要になります。
行数は省略できる場面がありますが、列数は基本的に必要です。
サイズが最初から決まっている場合は、通常の二次元配列が使いやすいです。
一方で、行数や列数が実行時に決まる場合は、vectorを使うのが一般的です。
vectorを使えば、二次元配列のように行と列を持つデータを柔軟に扱えます。
ただし、厳密には通常の二次元配列ではなく、vectorの中にvectorが入っている構造です。
初心者のうちは、まず次の考え方を押さえておくとよいでしょう。
- 固定サイズのデータには通常の二次元配列を使います。
- 実行時にサイズが決まるデータにはvectorを使います。
- 添字は0から始まります。
- 初期化せずに使うと危険な場合があります。
- 範囲外アクセスは未定義動作になるため注意が必要です。
これらを理解しておけば、C++で表形式のデータや盤面データを扱う基礎が身につきます。
以上、C++の二次元配列の宣言についてでした。
最後までお読みいただき、ありがとうございました。
