
はじめての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 ~/Desktop2. コンパイルする
clang final_exercise.c -o final_exercise3. 実行する
./final_exercise実行例:
name: Alice
score: 80
name: Bob
score: 90
name: Carol
score: 70
Alice 80
Bob 90
Carol 70
sum: 240
average: 80.04. 保存されたファイルを確認する
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].name は char 配列なので、& は付けません。
%19s は、最大19文字まで読み取る指定です。
name は20文字分の配列なので、最後の '\0' の分を残しています。
scanf(“%d”, &students[i].score)
scanf("%d", &students[i].score);点数を整数として読み取ります。
score は int 型のメンバなので、& を付けてアドレスを渡します。
sum_scores
int sum_scores(struct Student students[], int size)点数の合計を求める関数です。
合計値を return で返すため、戻り値の型は int です。
(double)sum / 3
average = (double)sum / 3;平均点を小数で計算するために、sum を double に変換しています。
整数同士の割り算にすると、小数部分が切り捨てられるためです。
save_result
void save_result(struct Student students[], int size, int sum, double average)結果をファイルに保存する関数です。
fopen で result.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].name は char 配列です。
scanf("%s", students[i].name); のように、配列名をそのまま渡します。
一方、students[i].score は int 型なので、& が必要です。
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配列に文字列を入力するときは、配列名をそのまま渡す- ファイルに保存するときは
fopen、fprintf、fcloseを使う - 処理を関数に分けると、プログラム全体の流れが読みやすくなる
この回では、これまで学んだ内容を組み合わせて、学生の点数を管理する小さなプログラムを作りました。
入力、配列、関数、構造体、ファイル入出力を組み合わせることで、実用的な処理に近いプログラムになります。
次回予告
この連載では、C言語の基本文法から始めて、ポインタ、配列、文字列、構造体、ファイル入出力、動的メモリ確保まで扱いました。
次は実践編として、入力エラー処理をより丁寧にする、複数ファイルに分割する、動的メモリ確保で人数を可変にする、といった改良に取り組むことで理解を深めます。


