🗃️ 配列
🎯 2次元配列を宣言・初期化し、二重ループで表として扱える
配列の要素を、さらに配列にできます。こうしてできるのが多次元配列で、いちばん出番が多いのは2次元です。表・マス目・座席表・画像——縦横に並ぶものは、たいていこれで表せます。
int m[2][3]; // 2行3列の表(要素は 2 × 3 = 6個)読み方は「int が3個入った配列が、2個並んだもの」。先の [2] が行数、後ろの [3] が列数です。アクセスは m[行][列] の順で、どちらの添字も0始まりです。
どんなときに使うか
「行と列がある」ものはすべて2次元配列の出番です。カレンダー、座席表、迷路のマス目、オセロの盤面、画像の画素、行列の計算。紙に書いたときに表になるデータなら、素直に [行][列] で持つのがいちばん読みやすくなります。
#include <stdio.h>
int main(void)
{
int m[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("%d\n", m[0][0]); // 1
printf("%d\n", m[1][2]); // 6
for (int r = 0; r < 2; r++) {
for (int c = 0; c < 3; c++) {
printf("%3d", m[r][c]);
}
printf("\n");
}
return 0;
}初期化は内側の { } で行ごとに区切ると、見た目がそのまま表になって読みやすくなります。ここでも足りないぶんは0で埋まります。
二重ループとの相性
外側のループで行、内側のループで列を回すのが定石です。ループ変数を i j にすると取り違えやすいので、r(row)と c(column)のように意味のある名前にすると事故が減ります。
#include <stdio.h>
int main(void)
{
int table[9][9];
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
table[r][c] = (r + 1) * (c + 1); // 九九表
}
}
printf("%d\n", table[6][7]); // 7 × 8 = 56
return 0;
}本当は一列に並んでいる
int m[2][3] はメモリ上で 6個の int が一列に並んでいます。並び順は m[0][0], m[0][1], m[0][2], m[1][0], … と、行を先に埋めていく順(行優先)です。表に見えているのは人間の都合で、実体は1本の帯だと知っておくと、第2部でポインタを学ぶときにすんなり繋がります。
この性質を利用して、int m[3][4] の代わりに int m[12] を用意し、m[r * 4 + c] と自分で計算する書き方もできます。メモリ上の並びはまったく同じです。ただし添字の計算を1つ間違えれば即座に境界外アクセスになるので、2次元で書けるところは素直に2次元で書きましょう。
罠・注意
m[1, 2] という書き方は間違いです。Cではカンマ演算子と解釈され、意図しない動作になります。必ず m[1][2] と書きます。m[2][3] に対して m[2][0] は境界外——行の添字は 0 と 1 しかありません。{ } を省いて int m[2][3] = {1, 2, 3, 4, 5, 6}; と書くこともできます。並び順どおりに詰められるので結果は同じですが、行の区切りが見えなくなるため、行ごとに { } で囲む書き方をおすすめします。sizeof(m) / sizeof(m[0]) で行数、sizeof(m[0]) / sizeof(m[0][0]) で列数が求まります。まとめ: 2次元配列は 型 名前[行][列]、アクセスは 名前[行][列]。二重ループで回し、メモリ上は行優先で一列に並んでいます。
配列の要素数を超えた添字にアクセスしてしまう重大な事故のこと。コンパイルは通ってしまうため、実行時に予期せぬ動作を引き起こす。他の変数の領域を書き換えてしまい、原因の分かりにくいバグにつながりやすい。
もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。