プログラミング

C言語超入門⑥ 配列と文字列を使おう

複数の値をまとめる配列、添字、文字列、fgets、strlenの基本を短いコードで学びます。

5人分の点数をscore1、score2、score3……と保存すると、人数が増えるたびに変数と計算を書き足すことになります。それなら、同じ種類の値を順番に並べて扱いたいですよね。

そのための仕組みが配列です。今回は配列で点数を集計し、その考え方を文字列にも広げます。C言語の文字列には「終わりの目印」があることが重要です。

短いコード欄は、stdio.hを読み込んだプログラムのmain内へ書く部分です。前の例に続けると説明した箇所以外は、例ごとに分けて試してください。

1. 配列には0番から順番に値が入る

int scores[5] = {72, 88, 95, 64, 81};

scoresは、int型の値を5個保存する配列です。1個ずつの値を要素、位置の番号を添字(そえじ)と呼びます。

添字 0 1 2 3 4
値 72 88 95 64 81

scores[0]が最初、scores[2]が3番目です。5個あるからといってscores[5]が最後ではありません。

printf("最初: %d\n", scores[0]);
scores[1] = 90;
printf("2番目: %d\n", scores[1]);

出力は72と90です。配列全体ではなく、[1]の場所だけを書き換えています。

範囲外アクセスは、必ずエラーになるとは限らない

scores[5]やscores[-1]を読むと未定義動作になります。これは、結果が言語で保証されないという意味です。

たまたま数字が出ても、正しく動いている証拠にはなりません。自動で範囲を確認してくれる配列ではないため、0以上、要素数未満を自分で守ります。

2. for文で合計と平均を求める

#include <stdio.h>

int main(void) {
    int scores[5] = {72, 88, 95, 64, 81};
    int total = 0;

    for (int i = 0; i < 5; i++) {
        total += scores[i];
    }

    printf("合計: %d\n", total);
    printf("平均: %.1f\n", total / 5.0);
    return 0;
}
合計: 400
平均: 80.0

ループでiが0から4へ変化し、それに合わせてscores[i]が72、88、95、64、81へ切り替わります。

途中の合計は72 → 160 → 255 → 319 → 400です。total += scores[i]は、total = total + scores[i]の短い形でしたね。

total / 5.0では、小数の5.0を使うことで小数まで計算します。今回はちょうど割り切れますが、整数の5だけで割ると、別の点数では端数を失います。

sizeofは配列の大きさをバイトで調べる

配列がその場にあるなら、sizeof(scores) / sizeof(scores[0])で要素数を求められます。全体のサイズを、1要素のサイズで割るためです。

ただし、関数の引数に書いたint scores[]は配列そのものを丸ごと受け取る宣言ではなく、ポインタとして扱われます。その関数内のsizeof(scores)では元の配列の要素数は分かりません。関数へ配列を渡すときは、要素数も別に渡すことを覚えておきましょう。

3. C言語の文字列はcharの配列

char name[] = "Sora";
printf("%s\n", name);

C言語には、C++のstd::stringのような標準の文字列クラスはありません。終端文字を含むcharの並びを文字列として扱います。

"Sora"から作った配列は、次の5要素です。

添字 0 1 2 3 4
内容 'S' 'o' 'r' 'a' '\0'

'\0'は文字列の終わりを示す、値0の文字です。数字の文字'0'とは別物です。

printfの%sは、この終端までを表示します。だから「4文字なのに5個分の場所」が必要なんですね。終端のない配列を%sへ渡してはいけません。

宣言後は配列全体へ=で文字列を入れ直せない

char name[20] = "Sora";は初期化です。その後のname = "Ren";は書けません。

内容を変更するにはコピー用の関数などを使い、保存先に十分な容量があることを確認します。ポインタと配列を同じものとして考えると、ここで混乱しやすくなります。

4. fgetsで空白を含む1行を受け取る

#include <stdio.h>
#include <string.h>

int main(void) {
    char name[50] = "";

    printf("名前: ");
    if (fgets(name, sizeof(name), stdin) == NULL) {
        printf("入力を読み取れませんでした。\n");
        return 1;
    }

    name[strcspn(name, "\n")] = '\0';
    printf("こんにちは、%sさん。\n", name);
    return 0;
}

Sora Aoiと入力すれば、空白も含めて受け取れます。

fgetsの引数を分けて読みましょう。

  • name:文字を保存する配列
  • sizeof(name):配列の容量。今回は50バイト
  • stdin:標準入力。通常はキーボードからの入力

最大で容量より1つ少ない文字単位を読み、最後に'\0'を置きます。入力が途中で終わるなどして失敗するとNULLを返すため、先に成否を確認しています。Microsoftのfgetsの説明でも、改行と終端の扱いを確認できます。

改行を取り除くのはなぜ?

fgetsは読み取れた改行も配列に保存します。そのまま%sさんと表示すると、「さん」が次の行へ落ちることがあります。

strcspn(name, "\n")は、先頭から改行までの位置を求めます。そこを'\0'へ変えると、改行の前で文字列が終わります。改行がなければ元の終端位置を返すので、その位置を再び終端にしています。

この説明だけで少し複雑なら、まずこの1行を「末尾の改行を消す処理」として使い、あとでstring.hの関数を調べれば大丈夫です。

長すぎる入力は、全部読めない

name[50]に50バイト以上を入力すると、残りが入力側に残る場合があります。fgetsを1回呼べば常に1行全部が入る、という意味ではありません。

今回の例では短い名前を入力してください。長い入力を断り、残りを取り除いて再入力させる方法は、最終回で扱います。

また、数字用のscanfの後にfgetsを使うと、残っていた改行を先に読むことがあります。入力のたびに別の方法へ切り替えるより、1行ずつ読む方式へそろえると整理しやすくなります。

5. strlenは見た目の文字数とは限らない

#include <stdio.h>
#include <string.h>

int main(void) {
    char word[] = "code";

    printf("長さ: %zu\n", strlen(word));
    printf("配列の容量: %zu\n", sizeof(word));
    return 0;
}
長さ: 4
配列の容量: 5

strlenは最初の'\0'より前のバイト数を数え、終端は含めません。sizeofは配列全体なので終端分も含みます。結果の型size_tに対応する表示指定が%zuです。

英数字なら見た目の文字数と一致しやすいですが、UTF-8の"種"は通常3バイトです。「日本語何文字まで」をstrlenだけで判定することはできません。

配列・文字列の定義はC11公開委員会草案の6.7.6.2と7.24が一次資料です。

6. 練習:配列から最大値を探そう

5個の整数{12, 7, 24, 9, 18}から、最も大きい値を探してください。最大値を固定で24と書かず、ループで求めます。

模範解答

#include <stdio.h>

int main(void) {
    int numbers[5] = {12, 7, 24, 9, 18};
    int maximum = numbers[0];

    for (int i = 1; i < 5; i++) {
        if (numbers[i] > maximum) {
            maximum = numbers[i];
        }
    }

    printf("最大値: %d\n", maximum);
    return 0;
}

解説

最初の12を暫定1位にします。7は小さいので変更せず、24で更新。9と18は24を超えないため、最後に24が残ります。

最初の要素はすでに使ったので、ループをi = 1から始めています。

maximum = 0にしないのは、全要素が負の場合に0を誤って返してしまうからです。{-8, -3, -10, -6, -7}へ変えた場合の答えは-3です。配列が空の場合は最初の要素を読めませんが、この例は5個あると決めています。

次の一歩

配列では「何個あるか」と「何番を読むか」、文字列ではそれに「どこで終わるか」が加わります。この3つを意識すると、Cのデータの置き方が少しずつ見えてきます。

前の回 / 次の回:ポインタ

← 記事一覧へ戻る