第20回|総合演習:C言語で入力、配列、関数、構造体、ファイル入出力を組み合わせる

当サイトでは、コンテンツの一部に広告を掲載しています。
第20回|総合演習:C言語で入力、配列、関数、構造体、ファイル入出力を組み合わせる

はじめてのC言語 | 第20回

目次

はじめに

前回は、動的メモリ確保の基本を学びました。
今回は、ここまで学んだ内容を組み合わせて、小さなプログラムを作ります。

この回では、学生の名前と点数を入力し、一覧、合計点、平均点を表示し、結果をファイルに保存するプログラムを作ります。

この回の目的は次の5点です。

  • これまで学んだ文法を組み合わせて使う
  • 構造体で複数の値をまとめる
  • 配列と関数を組み合わせて処理する
  • scanf で入力を受け取る
  • fprintf を使って結果をファイルに保存する

今回作るプログラム

今回作るプログラムでは、次の処理を行います。

  • 学生3人分の名前と点数を入力する
  • 入力された学生情報を画面に表示する
  • 合計点を計算する
  • 平均点を計算する
  • 結果を result.txt に保存する

ここまでの回で学んだ、構造体、配列、関数、入力、ファイル出力をまとめて使います。

プログラム全体

ソースコード

final_exercise.c という名前で保存します。

#include <stdio.h>

struct Student {
    char name[20];
    int score;
};

void input_students(struct Student students[], int size) {
    int i;

    for (i = 0; i < size; i++) {
        printf("name: ");
        scanf("%19s", students[i].name);

        printf("score: ");
        scanf("%d", &students[i].score);
    }
}

void print_students(struct Student students[], int size) {
    int i;

    for (i = 0; i < size; i++) {
        printf("%s %d\n", students[i].name, students[i].score);
    }
}

int sum_scores(struct Student students[], int size) {
    int i;
    int sum = 0;

    for (i = 0; i < size; i++) {
        sum += students[i].score;
    }

    return sum;
}

void save_result(struct Student students[], int size, int sum, double average) {
    FILE *fp;
    int i;

    fp = fopen("result.txt", "w");
    if (fp == NULL) {
        printf("file open error\n");
        return;
    }

    for (i = 0; i < size; i++) {
        fprintf(fp, "%s %d\n", students[i].name, students[i].score);
    }

    fprintf(fp, "sum %d\n", sum);
    fprintf(fp, "average %.1f\n", average);

    fclose(fp);
}

int main(void) {
    struct Student students[3];
    int sum;
    double average;

    input_students(students, 3);
    print_students(students, 3);

    sum = sum_scores(students, 3);
    average = (double)sum / 3;

    printf("sum: %d\n", sum);
    printf("average: %.1f\n", average);

    save_result(students, 3, sum, average);

    return 0;
}

実行手順

1. 作業ディレクトリに移動する

cd ~/Desktop

2. コンパイルする

clang final_exercise.c -o final_exercise

3. 実行する

./final_exercise

実行例:

name: Alice
score: 80
name: Bob
score: 90
name: Carol
score: 70
Alice 80
Bob 90
Carol 70
sum: 240
average: 80.0

4. 保存されたファイルを確認する

cat result.txt

実行結果:

Alice 80
Bob 90
Carol 70
sum 240
average 80.0

コードの読み方

struct Student

struct Student {
    char name[20];
    int score;
};

学生1人分の情報を表す構造体です。
name に名前、score に点数を保存します。

struct Student students[3]

struct Student students[3];

学生3人分の情報を保存する構造体配列です。
students[0]students[1]students[2] に、それぞれ1人分の情報を保存します。

input_students

void input_students(struct Student students[], int size)

学生情報を入力する関数です。
構造体配列と要素数を受け取り、for 文で1人ずつ入力します。

scanf(“%19s”, students[i].name)

scanf("%19s", students[i].name);

名前を文字列として読み取ります。
students[i].namechar 配列なので、& は付けません。

%19s は、最大19文字まで読み取る指定です。
name は20文字分の配列なので、最後の '\0' の分を残しています。

scanf(“%d”, &students[i].score)

scanf("%d", &students[i].score);

点数を整数として読み取ります。
scoreint 型のメンバなので、& を付けてアドレスを渡します。

sum_scores

int sum_scores(struct Student students[], int size)

点数の合計を求める関数です。
合計値を return で返すため、戻り値の型は int です。

(double)sum / 3

average = (double)sum / 3;

平均点を小数で計算するために、sumdouble に変換しています。
整数同士の割り算にすると、小数部分が切り捨てられるためです。

save_result

void save_result(struct Student students[], int size, int sum, double average)

結果をファイルに保存する関数です。
fopenresult.txt を開き、fprintf で内容を書き込み、最後に fclose で閉じます。

関数に分ける理由

今回のプログラムでは、処理を次の関数に分けています。

  • input_students : 入力を担当する
  • print_students : 表示を担当する
  • sum_scores : 合計計算を担当する
  • save_result : ファイル保存を担当する

1つの main 関数にすべてを書くこともできます。
しかし、処理を関数に分けると、各処理の役割が分かりやすくなります。

入力エラーを確認する版

実用的には、scanf の戻り値も確認します。
次は点数入力の失敗を確認する例です。

printf("score: ");
if (scanf("%d", &students[i].score) != 1) {
    printf("input error\n");
    return;
}

今回の本体コードでは、全体を読みやすくするために基本形を優先しています。
実際のプログラムでは、入力エラーの確認を入れるほうが安全です。

初心者がつまずきやすい点

文字列入力には & を付けない

students[i].namechar 配列です。
scanf("%s", students[i].name); のように、配列名をそのまま渡します。

一方、students[i].scoreint 型なので、& が必要です。

scanf("%d", &students[i].score);

平均計算で整数同士の割り算をしてしまう

次のように書くと、整数同士の割り算になります。

average = sum / 3;

小数まで出したい場合は、少なくとも片方を double にします。

average = (double)sum / 3;

ファイルを閉じ忘れる

fopen で開いたファイルは、使い終わったら fclose で閉じます。

fclose(fp);

配列の要素数を関数に渡し忘れる

構造体配列を関数に渡すときも、要素数を一緒に渡します。

print_students(students, 3);

よくあるエラー

file open error と表示される

原因: result.txt を作成または上書きできません。
対処: 実行しているディレクトリや権限を確認します。

名前が途中までしか入力されない

原因: %s は空白で区切られます。
対処: この回では、名前に空白を含めない入力を前提にします。

平均点が 80.0 ではなく 80 のように表示される

原因: printf の書式指定子が整数用になっている可能性があります。
対処: double を表示するときは %f を使います。

printf("average: %.1f\n", average);

実行結果がおかしい

原因: 配列の範囲外にアクセスしている可能性があります。
対処: ループ条件が i < size になっているか確認します。

練習用コード

最大点を求める関数を追加する

次の関数を追加して、最高点を表示してみます。

int max_score(struct Student students[], int size) {
    int i;
    int max = students[0].score;

    for (i = 1; i < size; i++) {
        if (students[i].score > max) {
            max = students[i].score;
        }
    }

    return max;
}

main 関数から次のように呼び出します。

printf("max: %d\n", max_score(students, 3));

保存する内容に最大点を追加する

save_result の引数に最大点を追加し、ファイルにも保存してみます。

fprintf(fp, "max %d\n", max);

このように、機能を追加するときは、必要な値をどの関数に渡すかを考えます。

まとめ

今回のポイントは次のとおりです。

  • 構造体を使うと、関連する値を1つのデータとして扱える
  • 構造体配列を使うと、複数件のデータをまとめて扱える
  • 配列を関数に渡すときは、要素数も一緒に渡す
  • scanf で整数を受け取るときは & が必要である
  • char 配列に文字列を入力するときは、配列名をそのまま渡す
  • ファイルに保存するときは fopenfprintffclose を使う
  • 処理を関数に分けると、プログラム全体の流れが読みやすくなる

この回では、これまで学んだ内容を組み合わせて、学生の点数を管理する小さなプログラムを作りました。
入力、配列、関数、構造体、ファイル入出力を組み合わせることで、実用的な処理に近いプログラムになります。

次回予告

この連載では、C言語の基本文法から始めて、ポインタ、配列、文字列、構造体、ファイル入出力、動的メモリ確保まで扱いました。

次は実践編として、入力エラー処理をより丁寧にする、複数ファイルに分割する、動的メモリ確保で人数を可変にする、といった改良に取り組むことで理解を深めます。

あわせて読みたい
実践編1|C言語でscanf のエラー処理を丁寧にする:入力失敗を確認して安全に処理する はじめてのC言語 | 第21回 はじめに 第20回では、入力、配列、関数、構造体、ファイル入出力を組み合わせた総合演習を行いました。今回は、その総合演習を少し実用的...

さらに学びたいあなたへ

用途ごとに選ぶ Linux のおすすめ本

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

Created by UNIX Cafe

目次