6日でできる 新C言語入門|行と列で学ぶ2次元配列

行と列で考えれば、2次元配列は表のように見えてくる。
マス目の感覚でデータを整理し、C言語で表形式の処理を書けるようになろう。

C言語で複数のデータをまとめて扱うとき、まず登場するのが1次元配列です。1次元配列は、値が横一列に並んだ箱のようなイメージで、点数や気温、価格の一覧などを扱うときに便利です。

しかし、実際のデータには、横一列だけでは表しにくいものもあります。たとえば、座席表、成績表、売上一覧、ゲームのマップ、画像のピクセル情報などは、縦と横の形で整理したほうが自然です。

このような行と列を持つデータを扱うときに使うのが2次元配列です。

2次元配列は、表のように縦横に並んだデータを管理できる配列です。1次元配列では添え字を1つ使って要素を指定しましたが、2次元配列では行と列の2つの添え字を使います。

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

int a[3][4];

この場合、aは3行4列のint型配列です。要素を指定するときは、a[行][列]の形で書きます。

2次元配列は、for文の2重ループと組み合わせることで、とても扱いやすくなります。外側のfor文で行を進め、内側のfor文で列を進めることで、表のすべてのマスを順番に処理できます。

この記事では、2次元配列の宣言、行と列の考え方、値の代入、初期化、for文の2重ループによる表示、添え字の注意点まで、サンプルプログラムを使ってやさしく解説していきます。

2次元配列は縦と横に並んだデータを扱う

2次元配列は、行と列を持つ配列です。表やマス目のようなデータを扱うときに向いています。

基本の宣言は次の形です。

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

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

int a[3][4];

これは、int型の値を3行4列、つまり合計12個扱える配列を宣言しているという意味です。

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

2次元配列では、1つの要素を指定するために、行番号と列番号を使います。

a[0][0] = 10;
a[1][2] = 7;

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

人間の感覚では、a[1][2]を2行3列目と考えることもできます。これは、C言語の添え字が0から始まるためです。

添え字は行も列も0から始まる

2次元配列を理解するうえで、特に大切なのが添え字の考え方です。

C言語では、配列の添え字は0から始まります。これは1次元配列でも2次元配列でも同じです。

たとえば、次の配列を考えます。

int a[3][4];

この配列は3行4列です。行番号と列番号の範囲は次のようになります。

項目有効な添え字
行番号0, 1, 2
列番号0, 1, 2, 3
最初の要素a[0][0]
最後の要素a[2][3]

表で表すと、次のようになります。

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

3行4列だからといって、a[3][4]まで使えるわけではありません。
有効なのは、行が0から2まで、列が0から3までです。

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

この図から分かること

この図から分かるのは、2次元配列では行と列の2つの番号を使って要素を指定するということです。

a[3][4]と宣言した場合、3行4列の表として考えられます。ただし、添え字は0から始まるため、行番号は0、1、2、列番号は0、1、2、3になります。

最後の要素はa[3][4]ではなく、a[2][3]です。2次元配列を扱うときは、行数と列数から有効な添え字の範囲を正しく考えることが大切です。

2次元配列に値を代入して表示する

まずは、2次元配列に値を代入し、その内容を表示するプログラムを見てみましょう。

ここでは、2行3列の配列を用意し、各要素に行番号と列番号から作った値を入れます。

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

#include <stdio.h>

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

    // 2行3列の各要素に、行と列が分かる値を代入する
    for (row = 0; row < 2; row++) {
        for (col = 0; col < 3; col++) {
            table[row][col] = (row + 1) * 10 + (col + 1);
        }
    }

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

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

    return 0;
}

実行結果

table[0][0]=11 table[0][1]=12 table[0][2]=13
table[1][0]=21 table[1][1]=22 table[1][2]=23

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

int table[2][3];

これは、2行3列のint型配列です。合計では、2 × 3 で6個の整数を扱えます。

値を代入している部分は、次の2重ループです。

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

rowは行番号、colは列番号を表します。
rowが0、colが0のときは、table[0][0]に11が入ります。rowが1、colが2のときは、table[1][2]に23が入ります。

このように、2次元配列では行と列の位置に応じて値を代入できます。

2重ループで2次元配列を順番に処理する

2次元配列は、for文の2重ループと組み合わせると扱いやすくなります。

外側のfor文では行を進めます。内側のfor文では、その行の中の列を進めます。

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

この流れを表にすると、次のようになります。

外側のrow内側のcolアクセスする要素
00, 1, 2table[0][0]、table[0][1]、table[0][2]
10, 1, 2table[1][0]、table[1][1]、table[1][2]

外側のrowが0のとき、内側のcolが0から2まで動きます。これで0行目の要素をすべて処理できます。
次にrowが1になり、またcolが0から2まで動きます。これで1行目の要素を処理できます。

このように、2次元配列では外側が行、内側が列と考えると、コードの流れが読みやすくなります。

改行の位置で表の見え方が変わる

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

先ほどのプログラムでは、改行のためのprintfを内側のfor文が終わったあとに書いています。

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

    printf("\n");
}

この位置に改行を書くことで、1行分の列をすべて表示してから次の行へ進めます。

もし改行を内側のfor文の中に書いてしまうと、1つの要素を表示するたびに改行され、表のような見た目になりません。

2次元配列を表示するときは、内側のfor文が1行分を担当し、そのあとで改行すると覚えておくと分かりやすいです。

図:2重ループで2次元配列を処理する流れ

この図から分かること

この図から分かるのは、2次元配列の処理では、外側のfor文と内側のfor文に役割があるということです。

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

また、内側のfor文が終わったあとに改行すると、配列の内容を表の形で表示できます。

2次元配列は宣言と同時に初期化できる

2次元配列は、宣言と同時にまとめて値を入れることができます。これを初期化と呼びます。

たとえば、2行4列の配列を初期化する場合は、次のように書けます。

int seat[2][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
};

この場合、1行目に1、2、3、4が入り、2行目に5、6、7、8が入ります。

表で見ると、次のようになります。

列0列1列2列3
行01234
行15678

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

{
    {1, 2, 3, 4},
    {5, 6, 7, 8}
}

このように行ごとに分けて書くと、配列の構造が表に近い形で見えるため、読みやすくなります。

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

次のプログラムでは、3人分の国語と数学の点数を2次元配列で管理し、表のように表示します。

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

#include <stdio.h>

int main(void) {
    int score[3][2] = {
        {82, 76},
        {90, 88},
        {68, 74}
    };

    int row, col;

    // 3人分、2科目の点数を表示する
    for (row = 0; row < 3; row++) {
        printf("%d人目: ", row + 1);

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

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

    return 0;
}

実行結果

1人目: 82点 76点
2人目: 90点 88点
3人目: 68点 74点

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

行を人、列を科目として考えると、次のように整理できます。

科目0科目1
0人目8276
1人目9088
2人目6874

表示では、row + 1を使って、画面上では1人目、2人目、3人目と表示しています。
配列の添え字は0から始まりますが、人に見せる表示では1から始めるほうが自然な場合があります。

行と列の意味を決めると分かりやすい

2次元配列では、行と列が何を表しているのかを決めておくと、コードが読みやすくなります。

たとえば、score[3][2]の場合、次のように考えられます。

添え字意味
score[row][col]row番目の人のcol番目の科目
row人を表す
col科目を表す

行と列の意味を決めておくと、配列を参照するときに迷いにくくなります。

score[row][col]

この形を見たときに、rowが人、colが科目だと分かっていれば、どのデータを扱っているのかを理解しやすくなります。

2次元配列では、単に数字の表として見るだけでなく、行と列に意味を持たせることが大切です。

2次元配列の初期化パターン

2次元配列には、いくつかの初期化パターンがあります。特に、要素数の省略については注意が必要です。

行数と列数を両方指定する

もっとも基本的な書き方です。

int n[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

この場合、2行3列の配列として初期化されます。

初期化する場合は行数を省略できる

初期化の値から行数が分かる場合、行数を省略できます。ただし、列数は書く必要があります。

int n[][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

この場合、列数が3であることが指定されています。初期化の内容から、行数は2と判断されます。

列数を省略することはできない

2次元配列では、列数を省略した宣言はできません。

int n[][];

このような書き方はできません。

C言語では、2次元配列の各行が何列分あるのかを知る必要があります。そのため、初期化で行数を省略する場合でも、列数は必ず指定します。

書き方可否説明
int n[2][3];可能行数と列数を指定
int n[2][3] = {{1, 2, 3}, {4, 5, 6}};可能行数と列数を指定して初期化
int n[][3] = {{1, 2, 3}, {4, 5, 6}};可能初期化時は行数を省略できる
int n[][];不可列数が分からないため使えない

添え字の範囲外アクセスに注意する

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

たとえば、次の配列を考えます。

int a[3][4];

この配列で有効な範囲は、a[0][0]からa[2][3]までです。

書き方状態
a[0][0]有効
a[2][3]有効
a[3][0]行が範囲外
a[0][4]列が範囲外
a[3][4]行も列も範囲外

行数が3なので、使える行番号は0、1、2です。
列数が4なので、使える列番号は0、1、2、3です。

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

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

この図から分かること

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

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

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

2次元配列とfor文の条件を合わせる

2次元配列をfor文の2重ループで処理するときは、行数と列数に合わせて条件式を書きます。

たとえば、int a[3][4]を処理するなら、次のように書きます。

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

row < 3によって、rowは0、1、2まで動きます。
col < 4によって、colは0、1、2、3まで動きます。

この条件にすることで、a[0][0]からa[2][3]まで、範囲内の要素だけを処理できます。

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

for文の条件は、配列のサイズと必ず対応させましょう。
row <= 3やcol <= 4のように書いてしまうと、範囲外アクセスにつながる可能性があります。

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

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

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

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

2次元配列を使うときは、配列の行と列が何を表しているのかを決めてから書くと、コードの意味がはっきりします。

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

2次元配列は便利ですが、行と列が混乱しやすいところでもあります。読みやすく書くためには、次の点を意識しましょう。

行と列の変数名を分かりやすくする

rowとcolのような変数名を使うと、どちらが行でどちらが列なのか分かりやすくなります。

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

初期化は行ごとに分けて書く

2次元配列を初期化するときは、行ごとに波かっこを分けると、表の形が見えやすくなります。

int seat[2][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
};

改行の位置を意識する

表として表示する場合は、内側のfor文が終わったあとに改行します。

添え字の最大値を確認する

行数が3なら最後の行番号は2、列数が4なら最後の列番号は3です。要素数そのものを添え字にしないように注意しましょう。

行と列で考えると2次元配列は扱いやすくなる

2次元配列は、表やマス目のようなデータを扱うための配列です。1次元配列が横一列のデータを管理するのに対して、2次元配列は行と列を使って縦横のデータを整理できます。

2次元配列の基本は、型 配列名[行数][列数]; の形で宣言し、配列名[行][列]で要素を指定することです。行も列も添え字は0から始まるため、最後の要素は配列名[行数 - 1][列数 - 1]になります。

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

2次元配列に慣れるためには、まず小さな表を作り、rowとcolがどのように変化しているかを確認するのがおすすめです。行と列の感覚が身につくと、成績表、座席表、売上一覧、ゲームのマップなど、さまざまなデータを自然に扱えるようになります。