C言語の基本|2次元配列の文字列を表示する

1行ずつ取り出せば、複数の文字列もすっきり表示。2次元char配列の読み方を身につけましょう。

C言語では、1つの文字列をchar配列で表します。複数の文字列をまとめて管理したい場合は、char型の2次元配列が便利です。

たとえば、次のようなデータをまとめて保存できます。

  • 複数の駅名
  • 商品名の一覧
  • 曜日の名前
  • ユーザー名
  • 状態を表すラベル

2次元char配列は、行と列を持つ表のような構造です。文字列を保存するときは、1行に1つの文字列を入れると考えます。

たとえば、次の配列には3つの駅名が入っています。

char stations[][10] = {"Tokyo", "Ueno", "Omiya"};

それぞれの行は、次の文字列を表します。

表す文字列
stations[0]Tokyo
stations[1]Ueno
stations[2]Omiya

stations[0]はTという1文字ではなく、Tokyoという1行分の文字列を表します。

この考え方が分かると、printfと %s を使った表示や、for文による繰り返し表示も自然に理解できるようになります。

2次元char配列の各行は1つの文字列

まず、次の2次元char配列を見てみましょう。

char animals[][7] = {"Dog", "Rabbit", "Panda"};

この配列には、Dog、Rabbit、Pandaという3つの文字列が格納されています。

行番号配列の指定格納される文字列
0animals[0]Dog
1animals[1]Rabbit
2animals[2]Panda

最初の要素数は、文字列の個数を表します。2つ目の要素数は、1つの文字列を格納するための配列サイズです。

char 配列名[文字列の個数][1文字列分の配列サイズ];

今回の宣言では行数を省略していますが、初期値が3つあるため、コンパイラが3行の配列だと判断します。

列数は7です。そのため、各行には7個のchar型要素が用意されます。

図1:2次元char配列の1行と1つの文字列

この図から分かること

2次元char配列では、1行が1つの文字列として使われます。

animals[0]にはDog、animals[1]にはRabbit、animals[2]にはPandaが格納されています。

各行には同じ数の要素が用意されます。Rabbitは6文字なので、文字列の終端を表す \0 を含めると7要素必要です。この最も長い文字列に合わせて、列数を7にしています。

配列の内部では文字が1つずつ並んでいる

animalsの内部を表にすると、次のようになります。

要素0要素1要素2要素3要素4要素5要素6
animals[0]Dog\0000
animals[1]Rabbit\0
animals[2]Panda\00

Dogは3文字なので、要素3に \0 が入ります。それより後ろの要素も、文字列リテラルによる初期化で値が指定されなかったため0になります。

Rabbitは6文字なので、最後の要素6に \0 が入ります。列数が7より小さいと、Rabbitを文字列として格納するための領域が足りません。

行と列の役割

2次元char配列では、行と列の役割を分けて考えると分かりやすくなります。

項目役割
どの文字列を選ぶか
文字列内のどの文字を選ぶか
行数保存できる文字列の個数
列数1つの文字列を保存するための配列サイズ

animals[1]は、2行目の文字列Rabbitを表します。

animals[1]

さらに列の添字まで指定すると、Rabbitの中にある1文字を表します。

animals[1][0]

animals[1][0]は、Rabbitの先頭文字Rです。

animals[1][2]

animals[1][2]は、Rabbitの3文字目bです。

1行全体と1文字の違い

2次元char配列では、添字をいくつ指定するかによって意味が変わります。

書き方表すもの表示に使う書式指定子
animals[0]1行目の文字列Dog%s
animals[1]2行目の文字列Rabbit%s
animals[0][0]1行目の先頭文字D%c
animals[1][2]2行目の3文字目b%c

文字列全体を表示する場合は、行だけを指定します。

printf("%s\n", animals[0]);

1文字を表示する場合は、行と列の両方を指定します。

printf("%c\n", animals[0][0]);

%sで1行分の文字列を表示する

2次元char配列の各行は、printfと %s を使って表示できます。

printf("%s\n", animals[0]);
printf("%s\n", animals[1]);
printf("%s\n", animals[2]);

実行結果は次のようになります。

Dog
Rabbit
Panda

%sは、指定された位置から文字を順番に読み取り、\0が見つかったところで表示を終了します。

animals[0]を指定した場合は、次の順番で読み取られます。

  • Dを表示する
  • oを表示する
  • gを表示する
  • \0を見つける
  • 表示を終了する

animals[0]は、1行目の先頭位置を表します。そのため、%sに渡すと、1行目に格納されている文字列全体を表示できます。

char型の2次元配列と数値の2次元配列

int型の2次元配列では、通常、行と列の両方を指定して1つの数値を取り出します。

int numbers[2][3] = {
    {10, 20, 30},
    {40, 50, 60}
};

printf("%d\n", numbers[0][1]);

この場合、numbers[0][1]が表す20を表示します。

char型の2次元配列でも、1文字を取り出すなら行と列の両方を指定します。ただし、文字列として表示するときは、1行全体を指定できます。

配列指定方法表すデータ
int型2次元配列numbers[0][1]1つの整数
char型2次元配列animals[0][1]1つの文字
char型2次元配列animals[0]1つの文字列

for文で各行を順番に表示する

文字列を1つずつ個別に表示することもできます。

printf("%s\n", animals[0]);
printf("%s\n", animals[1]);
printf("%s\n", animals[2]);

ただし、文字列の数が増えると、同じようなprintfを何度も書かなければなりません。

そこで、for文を使います。

for (int i = 0; i < 3; i++) {
    printf("%s\n", animals[i]);
}

変数iは、表示する行を表します。

iの値表示する行表示される文字列
0animals[0]Dog
1animals[1]Rabbit
2animals[2]Panda

iを0から2まで変化させることで、3行に格納されている文字列を順番に表示できます。

図2:for文で文字列を1行ずつ表示する流れ

この図から分かること

for文の変数iは、2次元char配列の行番号として使えます。

iが0ならanimals[0]、iが1ならanimals[1]、iが2ならanimals[2]が選ばれます。それぞれを %s で表示することで、各行に格納された文字列を順番に取り出せます。

この場合、列を処理するための内側のfor文は必要ありません。%sが、各行の先頭から \0 までをまとめて表示するからです。

なぜ二重ループを使わなくてもよいのか

2次元配列を扱うと聞くと、必ず二重ループが必要だと思うかもしれません。

文字を1つずつ処理する場合は、行と列を動かす二重ループを使えます。

for (int i = 0; i < 3; i++) {
    for (int j = 0; animals[i][j] != '\0'; j++) {
        printf("%c", animals[i][j]);
    }
    printf("\n");
}

この処理では、内側のfor文が各文字を1つずつ表示しています。

一方、文字列全体を %s で表示する場合は、内側のループをprintfに任せられます。

for (int i = 0; i < 3; i++) {
    printf("%s\n", animals[i]);
}
表示方法ループ特徴
%sで表示外側のfor文だけ1行をまとめて表示できる
%cで表示二重ループ1文字ずつ処理できる

単純に文字列全体を表示するなら、%sを使う方法が分かりやすく簡潔です。

列数の決め方

2次元char配列で特に注意したいのが、列数です。

次の配列を考えてみましょう。

char animals[][7] = {"Dog", "Rabbit", "Panda"};

各文字列に必要なサイズは次のとおりです。

文字列文字数\0を含む必要サイズ
Dog34
Rabbit67
Panda56

最も長いRabbitには7要素必要です。そのため、列数を7にしています。

列数は、次の考え方で決められます。

列数 = 最長の文字列の文字数 + 1

最後の1要素が、\0を格納するための領域です。

列数に余裕を持たせる場合

列数は、必要な最小サイズより大きくしても構いません。

char animals[][10] = {"Dog", "Rabbit", "Panda"};

この場合、各行に10要素分の領域が用意されます。

文字列使用する要素数残る領域
0Dog46
1Rabbit73
2Panda64

使用する要素数には、\0も含まれています。

列数を大きくするとメモリの使用量は増えますが、あとから少し長い文字列を格納できる余裕を持たせられます。

行数を省略できる理由

初期化する文字列が書かれている場合は、行数を省略できます。

char animals[][7] = {"Dog", "Rabbit", "Panda"};

初期値が3つあるので、コンパイラは3行必要だと判断できます。

次の2つの宣言は、同じ行数と列数を持ちます。

char animals[3][7] = {"Dog", "Rabbit", "Panda"};
char animals[][7] = {"Dog", "Rabbit", "Panda"};

一方、列数は省略できません。

char animals[][] = {"Dog", "Rabbit", "Panda"};

列数が分からなければ、1行あたりの大きさや、次の行が始まる位置を決められないからです。

要素数初期化時に省略できるか理由
行数できる初期値の個数から判断できる
列数できない1行分の大きさを決めるために必要

3つの駅名を順番に表示する

2次元char配列に格納した駅名を表示するプログラム

ファイル名:10_13_1.c

#include <stdio.h>

int main(void)
{
    /* 複数の駅名を2次元char配列で初期化する */
    char stations[][10] = {
        "Tokyo",
        "Ueno",
        "Omiya"
    };

    /* 各駅名を1行ずつ順番に表示する */
    for (int i = 0; i < 3; i++) {
        printf("%d番目の駅名: %s\n", i + 1, stations[i]);
    }

    return 0;
}
実行結果の例
1番目の駅名: Tokyo
2番目の駅名: Ueno
3番目の駅名: Omiya

プログラムの詳しい解説

最初に、3つの駅名を2次元char配列へ格納しています。

char stations[][10] = {
    "Tokyo",
    "Ueno",
    "Omiya"
};

初期値として3つの文字列があるため、行数は3になります。

格納される文字列
stations[0]Tokyo
stations[1]Ueno
stations[2]Omiya

列数は10です。最も長いTokyoとOmiyaは5文字なので、\0を含めても6要素あれば保存できます。今回は少し余裕を持たせて10要素にしています。

表示にはfor文を使っています。

for (int i = 0; i < 3; i++) {
    printf("%d番目の駅名: %s\n", i + 1, stations[i]);
}

iの値は0、1、2と変化します。

繰り返しiの値stations[i]表示番号
1回目0Tokyo1
2回目1Ueno2
3回目2Omiya3

配列の添字は0から始まりますが、人に見せる番号は1から始めたほうが自然です。そのため、表示番号にはi + 1を使っています。

stations[i]が表しているもの

for文の中にあるstations[i]は、i行目の文字列全体を表します。

iが0のときは、次の指定になります。

stations[0]

これはTokyoという文字列です。

iが1のときは、次の指定になります。

stations[1]

これはUenoという文字列です。

printfの %s は、stations[i]の先頭から \0 が見つかるまでを表示します。そのため、行の中にある文字列全体が出力されます。

1文字を取り出す場合

駅名の中から1文字だけを表示したい場合は、行と列の両方を指定します。

printf("%c\n", stations[0][0]);

このコードでは、Tokyoの先頭文字Tが表示されます。

printf("%c\n", stations[1][1]);

このコードでは、Uenoの2文字目eが表示されます。

指定表すデータ表示結果
stations[0]1行目の文字列Tokyo
stations[0][0]1行目の1文字目T
stations[1]2行目の文字列Ueno
stations[1][1]2行目の2文字目e

%sと %cを使い分ける

文字列全体と1文字では、printfで使う書式指定子が異なります。

表示するデータ指定例書式指定子
文字列全体stations[i]%s
1文字stations[i][j]%c

文字列全体を %c で表示したり、1文字を %s で表示したりすると、正しい結果になりません。

データの形に合わせて、%sと %cを使い分けましょう。

図3:1行の文字列と1つの文字の違い

この図から分かること

stations[i]のように行だけを指定すると、その行に格納された文字列全体を表します。この場合は %s を使って表示します。

stations[i][j]のように行と列の両方を指定すると、文字列内の1文字を表します。この場合は %c を使って表示します。

添字の数を見ることで、文字列全体を扱っているのか、1文字だけを扱っているのかを判断できます。

3つの動作状態を表示する

2次元char配列に格納した動作状態を表示するプログラム

ファイル名:10_13_2.c

#include <stdio.h>

int main(void)
{
    /* 複数の動作状態を2次元char配列で初期化する */
    char states[][7] = {
        "Ready",
        "Active",
        "Stop"
    };

    /* 各動作状態を順番に表示する */
    for (int i = 0; i < 3; i++) {
        printf("動作状態: %s\n", states[i]);
    }

    return 0;
}
実行結果の例
動作状態: Ready
動作状態: Active
動作状態: Stop

プログラムの詳しい解説

statesは、3つの動作状態を保存する2次元char配列です。

char states[][7] = {
    "Ready",
    "Active",
    "Stop"
};

各行には、次の文字列が入っています。

文字列文字数\0を含む必要サイズ
states[0]Ready56
states[1]Active67
states[2]Stop45

最も長いActiveは6文字です。末尾の \0 を含めると7要素必要なので、列数を7にしています。

for文では、iを0から2まで変化させています。

for (int i = 0; i < 3; i++) {
    printf("動作状態: %s\n", states[i]);
}

1回目はstates[0]、2回目はstates[1]、3回目はstates[2]が %s に渡されます。

このように、行数だけ繰り返せば、すべての文字列を順番に表示できます。

文字列の個数と列数を混同しない

2次元char配列では、行数と列数を取り違えないことが大切です。

char states[3][7];

この宣言の3と7には、次の意味があります。

数値意味
3保存できる文字列の個数
71つの文字列に使える要素数

3文字の文字列を7個保存するという意味ではありません。最大6文字までの文字列を3個保存できる配列です。7要素のうち、少なくとも1要素は \0 のために必要になります。

文字列を1行ずつ表示するときの基本形

2次元char配列の文字列を順番に表示する基本形は、次のようになります。

for (int i = 0; i < 行数; i++) {
    printf("%s\n", 配列名[i]);
}

この形では、次のように役割を分けます。

部分役割
i表示する行番号
i < 行数配列の範囲内だけ繰り返す
配列名[i]i行目の文字列
%s\0まで文字列を表示する

行数が3なら、iの範囲は0から2までです。条件をi <= 3にすると、存在しない4行目へアクセスしてしまうため注意しましょう。

よくあるつまずき

つまずきやすい点実際の考え方
words[i]を1文字だと思うwords[i]はi行目の文字列
words[i][j]を文字列だと思うwords[i][j]は1文字
文字列全体を %c で表示する文字列全体には %s を使う
1文字を %s で表示する1文字には %c を使う
列数を文字数と同じにする\0の分を加える
行数と列数を逆に考える行数は文字列の個数、列数は1文字列分のサイズ
for文をi <= 行数にするi < 行数にする
行数と列数を両方省略する省略できるのは初期化時の行数だけ

コードを読むときの確認手順

2次元char配列の文字列を表示するコードでは、次の順番で確認すると理解しやすくなります。

  • 配列にいくつの文字列が入っているか確認する
  • 1行あたりの列数を確認する
  • 最も長い文字列と \0 が収まるか確認する
  • 行ごとの文字列を表す配列名[i]を見つける
  • 文字列全体には %s が使われているか確認する
  • 1文字の場合は配列名[i][j]と %c が使われているか確認する
  • for文が0から行数未満まで繰り返しているか確認する

2次元char配列を読むときは、まず1行が1つの文字列という見方を持つことが大切です。この見方ができれば、配列名[i]で文字列を選び、printfと %s で表示する流れがすっきり理解できるようになります。