C言語基礎|文字列の配列

「文字列が増えたら、配列でまとめてスッキリ管理! “文字列の配列”で一気に扱おう」

文字列が1つなら配列、たくさんなら「配列の配列」

C言語では、文字列は char の配列で表しましたね。
ということは、文字列を何個もまとめたいなら、文字配列を何個も並べるのが自然です。

つまり、文字列の配列 = 配列の配列(2次元配列) です。
これを理解すると、メニュー一覧・名前リスト・単語帳みたいな「文字列の集まり」を、きれいに扱えるようになりますよ。

文字列の配列とは何か(全体像)

文字列の配列のイメージ(2次元配列)

図の説明

  • 1行が「1つの文字列」を表します。
  • 各行の末尾には \0(ナル文字)が入り、文字列の終わりを示します。
  • これを3行ぶん持てば「3個の文字列」をまとめて持てます。

代表的な宣言パターン(初期化あり/なし)

文字列の配列の宣言パターン

目的宣言例意味
初期化して用意char s[][8] = {"TOKYO", "OSAKA", "NAGOYA"};行数は初期化子の個数から決まる。列数8なので最大7文字+終端\0
入力で埋めるchar s[3][128];行数を省略できない。1行あたり最大127文字+終端\0

表の説明

  • 行数:文字列の個数
  • 列数:1つの文字列を入れる箱の大きさ(終端\0 も含める)

サンプルプログラム

地名のリストを表示するプログラムです。

プロジェクト名:chap9-6-1 ソースファイル名:chap9-6-1.c

// 文字列の配列を表示する(初期化版)

#include <stdio.h>

int main(void)
{
    char city[][10] = {"Tokyo", "Osaka", "Nagoya"};

    puts("----- 都市リスト -----");
    for (int i = 0; i < 3; i++) {
        printf("city[%d] = %s\n", i, city[i]);
    }

    return 0;
}

実行結果の例

----- 都市リスト -----
city[0] = Tokyo
city[1] = Osaka
city[2] = Nagoya

配列の要素の正体を分解して理解しよう

この宣言をもう一度見ます。

char city[][10] = {"Tokyo", "Osaka", "Nagoya"};

city の「型」の見え方

名前何の配列?要素1個の型要素数
city文字列の配列char[10]3

表の説明

  • city は配列
  • その要素(city[0], city[1], city[2])は、それぞれ char[10] の配列
  • char[10] は 10文字分の箱なので、最大9文字+終端\0 まで格納できます

2つの添字で「文字」まで取り出せる

文字列の配列は 2次元配列なので、2つの添字で中身の1文字にアクセスできます。

添字アクセスの例

city[0]      -> "Tokyo"
city[0][0]   -> 'T'
city[0][1]   -> 'o'

図の説明

  • city[i] は「i番目の文字列(先頭アドレスのように扱える)」
  • city[i][j] は「i番目の文字列の j文字目」

文字列をキーボードから読み込む(入力版)

次は、初期化せずに入力で3つの文字列を入れる例です。

プロジェクト名:chap9-6-2 ソースファイル名:chap9-6-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 文字列の配列を読み込んで表示する

#include <stdio.h>

int main(void)
{
    char name[3][32];

    puts("3人のニックネームを入力してください。");
    for (int i = 0; i < 3; i++) {
        printf("name[%d] : ", i);
        scanf("%31s", name[i]);   // 最大31文字まで読み込む(終端\0の分を残す)
    }

    puts("----- 入力結果 -----");
    for (int i = 0; i < 3; i++) {
        printf("name[%d] = %s\n", i, name[i]);
    }

    return 0;
}

ここが大事:なぜ & を付けないの?

scanf で文字列を読むとき、格納先には「書き込み先のアドレス」が必要です。
でも name[i] は配列なので、式として使うと先頭要素へのポインタのように扱われます
だから &name[i] みたいにすると逆に型が合わず、危険になりやすいです。

scanf に渡す実引数の違い(感覚)

渡すもの意味だいたいの型のイメージ
name[i]i行目の先頭に書き込むchar* のように扱われる
&name[i]i行目「配列まるごと」へのアドレスchar (*)[32] のようになりがち

表の説明
scanf の %s が欲しいのは「文字を並べて書ける先頭位置」です。
だから name[i] がちょうどよい、という理解でOKです。

登場する命令の書式と「何をする命令か」

puts の書式と役割

  • 書式
    puts(文字列);
  • 何をする?
    文字列を表示し、最後に改行を付けます。見出し表示に便利です。

printf の書式と役割

  • 書式
    printf(書式文字列, 実引数1, 実引数2, ...);
  • 何をする?
    書式に合わせて整形して表示します。%s で文字列を表示できます。

scanf の書式と役割(今回のポイントつき)

  • 書式
    scanf(書式文字列, 格納先, ...);
  • 何をする?
    標準入力から読み取り、指定した格納先へ入れます。
    %s は「空白までの文字列」を読み込みます。
  • 安全のためのコツ
    %31s のように最大文字数を指定すると、配列あふれを防ぎやすいです。
    name[3][32] なら、終端\0の分も考えて 31 までが安心です。

つまずきポイント(実務でもよく出る)

注意点まとめ

注意起きがちなこと対策
列数が足りない途中で切れる、または危険最大文字数+1(終端\0)を確保
scanf("%s") は空白で止まる苗字 名前 みたいに入力できない空白を含むなら別の方法を検討
入力が長すぎるバッファオーバーフローの危険%31s のように幅指定を使う

表の説明
「列数=最大文字数+1」は超重要です。+1 は終端\0 のためです。

演習問題

演習9-3:終了文字列で入力を止めて、入力済みだけ表示せよ

  • 文字列の個数は 3 より大きくし、その値をオブジェクト形式マクロで定義する
  • 入力で END!! を読んだら、読み込みをそこで終了
  • 表示は END!! より前に入力されたものだけ

解答例

プロジェクト名:chap9-6-3 ソースファイル名:chap9-6-3.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define MAX_WORDS 5
#define BUF_SIZE  32

int main(void)
{
    char s[MAX_WORDS][BUF_SIZE];
    int count = 0;

    puts("単語を入力してください(END!! で終了)");

    for (int i = 0; i < MAX_WORDS; i++) {
        printf("s[%d] : ", i);
        scanf("%31s", s[i]);

        if (s[i][0] == 'E' && s[i][1] == 'N' && s[i][2] == 'D' && s[i][3] == '!' && s[i][4] == '!' && s[i][5] == '\0') {
            break;
        }
        count++;
    }

    puts("----- 入力された単語 -----");
    for (int i = 0; i < count; i++) {
        printf("%s\n", s[i]);
    }

    return 0;
}

解説

  • MAX_WORDS で「最大何個読むか」を決めています。
  • count は「END!! 以外で入力された個数」を覚えるための変数です。
  • 表示は count 個だけにすることで、END!! 自体を出さずに済みます。
  • scanf は %31s にして、BUF_SIZE 32 の範囲に収まるようにしています。

(※ ここでは文字列比較を分かりやすく手作業で書きました。次章以降で strcmp を学ぶともっとスッキリできます。)