6日でできる 新C言語入門|2次元配列とfor文の2重ループ

行と列でデータを整理すれば、表の処理がぐっと分かりやすくなる。
2次元配列とfor文の2重ループを理解して、表形式のデータを自在に扱えるようになろう。

C言語で複数のデータをまとめて扱うとき、まず基本になるのは1次元配列です。1次元配列を使うと、点数や気温、価格のような同じ型のデータを横一列に並べるように管理できます。

しかし、実際のデータは横一列だけで表せるものばかりではありません。たとえば、クラスごとの成績表、月ごとの売上表、座席表、行列データ、縦横のマス目を持つデータなどは、行と列の形で管理したほうが自然です。

このような場面で便利なのが2次元配列です。

2次元配列は、行と列の2つの添え字を使ってデータを管理する配列です。1次元配列が横一列のデータだとすると、2次元配列は表のように縦横に広がるデータだと考えると分かりやすいです。

たとえば、3行4列の整数データを扱いたい場合は、次のように宣言します。

int table[3][4];

この場合、tableは3行4列のint型配列です。要素を指定するときは、table[行][列]のように、行番号と列番号の2つを使います。

2次元配列は、for文の2重ループととても相性がよいです。外側のfor文で行を進め、内側のfor文で列を進めることで、表全体の要素を順番に処理できます。

この記事では、2次元配列の宣言、初期化、参照、for文の2重ループとの組み合わせ、添え字の注意点、3次元配列の考え方まで、サンプルプログラムを使いながらやさしく解説していきます。

2次元配列は行と列でデータを管理する

2次元配列は、行と列を持つ表のような配列です。基本の宣言は次の形になります。

型 配列名[行数][列数];

たとえば、3行4列の整数型配列を作る場合は、次のように書きます。

int table[3][4];

この場合、tableには3行分のデータがあり、それぞれの行に4つの列があります。合計では、3 × 4 で12個のint型データを扱えます。

宣言例意味
int table[3][4];3行4列のint型配列
double score[2][5];2行5列のdouble型配列
char mark[4][3];4行3列のchar型配列

2次元配列の要素にアクセスするときは、2つの添え字を使います。

table[0][0] = 10;
table[1][2] = 25;

table[0][0]は0行0列の要素、table[1][2]は1行2列の要素を表します。

ここで注意したいのは、1次元配列と同じように、2次元配列の添え字も0から始まるということです。人間の感覚では1行目、2行目と数えますが、C言語では0行目、1行目のように扱います。

行番号と列番号はどちらも0から始まる

2次元配列では、行と列の両方に添え字があります。そして、どちらも0から始まります。

たとえば、次のように宣言した場合を考えます。

int a[2][3];

この配列は2行3列です。使える行番号と列番号は次のようになります。

項目有効な添え字
行番号0, 1
列番号0, 1, 2
有効な範囲a[0][0]からa[1][2]まで

表で見ると、次のようなイメージです。

列0列1列2
行0a[0][0]a[0][1]a[0][2]
行1a[1][0]a[1][1]a[1][2]

2行3列だからといって、a[2][3]まで使えるわけではありません。行は0から1まで、列は0から2までです。この感覚に慣れることが、2次元配列を安全に使うための大切なポイントです。

図:2次元配列は行と列で要素を指定する

この図から分かること

この図から分かるのは、2次元配列では1つの要素を指定するために、行番号と列番号の2つが必要になるという点です。

a[0][0]は0行0列、a[1][2]は1行2列の要素を表します。行数が2の場合に使える行番号は0と1、列数が3の場合に使える列番号は0、1、2です。2次元配列を扱うときは、行も列も0から始まることを意識しましょう。

2次元配列を初期化して表示する

2次元配列は、宣言と同時に値を入れて初期化できます。ここでは、2クラス4人分の点数を表のように管理するプログラムを見てみましょう。

プロジェクト/ファイル名: Lesson32_1/main.c

#include <stdio.h>

int main(void) {
    int score[2][4] = {
        {78, 85, 90, 72},
        {88, 76, 95, 81}
    };

    int row, col;

    // 2クラス4人分の点数を表示する
    for (row = 0; row < 2; row++) {
        for (col = 0; col < 4; col++) {
            printf("score[%d][%d]=%d  ", row, col, score[row][col]);
        }

        // 1クラス分を表示したら改行する
        printf("\n");
    }

    return 0;
}

実行結果

score[0][0]=78  score[0][1]=85  score[0][2]=90  score[0][3]=72
score[1][0]=88  score[1][1]=76  score[1][2]=95  score[1][3]=81

このプログラムでは、score[2][4]という2次元配列を使っています。これは、2行4列のint型配列です。

int score[2][4] = {
    {78, 85, 90, 72},
    {88, 76, 95, 81}
};

最初の行には78、85、90、72が入り、次の行には88、76、95、81が入ります。

表として見ると、次のような形です。

0列目1列目2列目3列目
0行目78859072
1行目88769581

2次元配列の初期化では、内側の波かっこで1行分のデータを表すと考えると分かりやすいです。

for文の2重ループで全要素を表示する

2次元配列の全要素を表示するときは、for文の2重ループを使うのが自然です。

for (row = 0; row < 2; row++) {
    for (col = 0; col < 4; col++) {
        printf("%d ", score[row][col]);
    }
    printf("\n");
}

外側のfor文は行を担当します。今回の場合、rowは0から1まで変化します。

内側のfor文は列を担当します。colは0から3まで変化します。

つまり、rowが0のとき、colが0、1、2、3と動き、0行目の要素をすべて表示します。その後、rowが1になり、またcolが0、1、2、3と動いて、1行目の要素を表示します。

外側のrow内側のcolアクセスされる要素
00, 1, 2, 3score[0][0]からscore[0][3]
10, 1, 2, 3score[1][0]からscore[1][3]

2次元配列は、行と列を持つため、1つのfor文だけでは全体を処理しにくいです。外側で行、内側で列を進める2重ループを使うことで、表全体を順番に処理できます。

改行の位置で表の見た目が決まる

2次元配列を表のように表示するときは、改行の位置も大切です。

今回のプログラムでは、改行のprintfを内側のfor文が終わった後に書いています。

for (row = 0; row < 2; row++) {
    for (col = 0; col < 4; col++) {
        printf("score[%d][%d]=%d  ", row, col, score[row][col]);
    }

    printf("\n");
}

このように書くと、1行分の列をすべて表示してから改行されます。そのため、配列の行ごとに見やすく表示できます。

もし改行を内側のfor文の中に書くと、1つの要素を表示するたびに改行されてしまい、表の形が崩れます。

2次元配列を見やすく表示したいときは、1行分の表示が終わったところで改行すると覚えておきましょう。

2次元配列に計算した値を代入する

2次元配列は、初期化して使うだけでなく、for文の2重ループを使って値を代入することもできます。

次のプログラムでは、3行4列の配列に、行番号と列番号から計算した値を入れて表示します。

プロジェクト/ファイル名: Lesson32_2/main.c

#include <stdio.h>

int main(void) {
    int table[3][4];
    int row, col;

    // 行番号と列番号を使って、各要素に値を代入する
    for (row = 0; row < 3; row++) {
        for (col = 0; col < 4; col++) {
            table[row][col] = (row + 1) * 10 + (col + 1);
        }
    }

    // 配列の内容を表の形で表示する
    for (row = 0; row < 3; row++) {
        for (col = 0; col < 4; col++) {
            printf("%d ", table[row][col]);
        }

        // 1行分を表示したら改行する
        printf("\n");
    }

    return 0;
}

実行結果

11 12 13 14
21 22 23 24
31 32 33 34

このプログラムでは、table[3][4]という3行4列の配列を宣言しています。

int table[3][4];

その後、2重ループを使って各要素に値を入れています。

table[row][col] = (row + 1) * 10 + (col + 1);

rowとcolは0から始まりますが、表示する値として分かりやすくするために、row + 1とcol + 1を使っています。

たとえば、rowが0、colが0の場合は、次のように計算されます。

(row + 1) * 10 + (col + 1)
= (0 + 1) * 10 + (0 + 1)
= 11

rowが2、colが3の場合は、次のようになります。

(row + 1) * 10 + (col + 1)
= (2 + 1) * 10 + (3 + 1)
= 34

このように、行番号と列番号を使うと、配列の位置に応じた値を作ることができます。

図:for文の2重ループで2次元配列を処理する

この図から分かること

この図から分かるのは、2次元配列とfor文の2重ループの対応関係です。

外側のfor文はrowを変化させて行を進めます。内側のfor文はcolを変化させて列を進めます。rowとcolの組み合わせによって、table[row][col]のように各要素へ順番にアクセスできます。

2次元配列を扱うときは、外側が行、内側が列という考え方を持つと、コードの流れが読みやすくなります。

代入用の2重ループと表示用の2重ループ

Lesson32_2/main.cでは、2重ループが2回登場しています。

1つ目は、配列に値を代入するための2重ループです。

for (row = 0; row < 3; row++) {
    for (col = 0; col < 4; col++) {
        table[row][col] = (row + 1) * 10 + (col + 1);
    }
}

2つ目は、配列の内容を表示するための2重ループです。

for (row = 0; row < 3; row++) {
    for (col = 0; col < 4; col++) {
        printf("%d ", table[row][col]);
    }
    printf("\n");
}

どちらもrowとcolを使っていますが、目的が違います。

ループ目的
代入用の2重ループ各要素に値を入れる
表示用の2重ループ各要素の値を取り出して表示する

2次元配列では、配列全体に対して処理を行うことが多いため、2重ループは何度も登場します。代入、表示、合計計算、検索など、さまざまな場面で使われます。

2次元配列の添え字に関する注意

2次元配列では、行と列の両方で範囲外アクセスに注意する必要があります。

たとえば、次の宣言を考えます。

int a[2][3];

この場合、有効な範囲は次のとおりです。

項目有効な範囲
行番号0から1
列番号0から2
要素a[0][0]からa[1][2]

範囲外になる例は次のようなものです。

書き方問題点
a[2][0]行番号2は範囲外
a[0][3]列番号3は範囲外
a[2][3]行番号も列番号も範囲外

C言語では、範囲外アクセスを自動的に安全に止めてくれるとは限りません。別のデータを書き換えたり、異常終了したり、予期しない動作につながる可能性があります。

2次元配列では、行数と列数の両方を確認することが大切です。

for文の条件と配列サイズを合わせる

2次元配列を2重ループで処理するときは、for文の条件を配列サイズに合わせます。

たとえば、3行4列の配列なら、次のように書きます。

int table[3][4];

for (row = 0; row < 3; row++) {
    for (col = 0; col < 4; col++) {
        table[row][col] = 0;
    }
}

row < 3によって、rowは0、1、2になります。
col < 4によって、colは0、1、2、3になります。

もしrow <= 3やcol <= 4のように書いてしまうと、範囲外の添え字にアクセスする可能性があります。

配列宣言行の条件列の条件
int a[2][3];row < 2col < 3
int table[3][4];row < 3col < 4
double score[4][5];row < 4col < 5

配列の要素数とfor文の条件は、必ず対応させて考えましょう。

図:2次元配列の範囲外アクセスに注意する

この図から分かること

この図から分かるのは、2次元配列では行と列のどちらか一方でも範囲を超えると、範囲外アクセスになるということです。

int a[2][3]の場合、有効な行番号は0と1、有効な列番号は0、1、2です。a[2][0]は行番号が範囲外であり、a[0][3]は列番号が範囲外です。

2次元配列を扱うときは、行方向と列方向の両方で、添え字が正しい範囲に収まっているかを確認しましょう。

2次元配列が向いているデータ

2次元配列は、行と列で整理できるデータに向いています。

データ例行のイメージ列のイメージ
成績表学生教科
売上一覧商品
座席表縦の列横の列
ゲームのマップ縦方向横方向
行列計算

2次元配列を使うと、表形式のデータをプログラムの中で扱いやすくなります。

たとえば、成績表ならscore[学生番号][教科番号]のように考えられます。座席表ならseat[行][列]のように考えられます。

2次元配列では、どちらの添え字が何を意味しているのかを分かりやすく決めておくと、コードが読みやすくなります。

3次元以上の配列も考え方は同じ

C言語では、2次元配列だけでなく、3次元以上の配列も作れます。

たとえば、次のように書くと、2 × 3 × 4のfloat型配列を宣言できます。

float data[2][3][4];

3次元配列では、添え字が3つになります。

data[0][1][2] = 12.5f;

考え方としては、行と列に加えて、さらに階層やページのような方向が増えるイメージです。

配列イメージ
1次元配列横一列のデータ
2次元配列行と列を持つ表
3次元配列表が複数重なったデータ

3次元配列も便利な場面はありますが、添え字が増えるほど管理は複雑になります。学習段階では、まず2次元配列をしっかり理解することが大切です。

2次元配列を読みやすく使うコツ

2次元配列は便利ですが、行と列の意味が分かりにくいと、コードを読むのが大変になります。読みやすく書くためには、次の点を意識しましょう。

rowとcolのような変数名を使う

行を表す変数にはrow、列を表す変数にはcolを使うと、役割が分かりやすくなります。

for (row = 0; row < 3; row++) {
    for (col = 0; col < 4; col++) {
        printf("%d ", table[row][col]);
    }
}

行数と列数をコメントで補足する

学習段階では、配列の宣言にコメントを付けると理解しやすくなります。

int table[3][4];  // 3行4列の配列

改行の位置を意識する

表として表示する場合は、内側のfor文が終わった後に改行を書くと、行ごとに見やすく表示できます。

範囲外アクセスに注意する

行番号も列番号も、0から要素数 - 1までが有効です。for文の条件が配列サイズと合っているかを確認しましょう。

2次元配列と2重ループを理解すると表形式の処理が書きやすくなる

2次元配列は、行と列を持つデータを管理するための配列です。成績表、売上一覧、座席表、ゲームのマップなど、表の形で考えられるデータを扱うときに便利です。

2次元配列では、配列名[行][列]の形で要素を指定します。行も列も添え字は0から始まるため、要素数と有効な範囲をしっかり意識する必要があります。

また、2次元配列はfor文の2重ループと組み合わせることで、すべての要素を順番に処理できます。外側のfor文で行を進め、内側のfor文で列を進めるという考え方を身につけると、表全体の表示や値の代入、合計計算などが書きやすくなります。

まずは小さな行数と列数の配列から練習し、rowとcolがどのように変化しているかを確認しながら、2次元配列の感覚をつかんでいきましょう。