
はじめてのC言語 | 第21回
はじめに
第20回では、入力、配列、関数、構造体、ファイル入出力を組み合わせた総合演習を行いました。
今回は、その総合演習を少し実用的にするために、scanf のエラー処理を丁寧にします。
scanf は便利ですが、入力が想定どおりでない場合に失敗することがあります。
その失敗を確認せずに処理を続けると、意図しない値を使ってしまう可能性があります。
この回の目的は次の5点です。
scanfの戻り値の意味を理解する- 整数入力に失敗した場合の処理を書く
- 点数の範囲チェックを行う
- 関数の戻り値で成功と失敗を表す
- 第20回の総合演習を安全な形に改良する
scanf の戻り値
scanf は、読み取りに成功した項目数を返します。
たとえば次のコードでは、整数を1個読み取れた場合に 1 が返ります。
int score;
if (scanf("%d", &score) == 1) {
printf("success\n");
}入力が整数でなかった場合、scanf("%d", &score) は 1 を返しません。
そのため、戻り値を確認することで入力エラーを検出できます。
まずは整数入力を確認する
ソースコード
scanf_check.c という名前で保存します。
#include <stdio.h>
int main(void) {
int score;
printf("score: ");
if (scanf("%d", &score) != 1) {
printf("input error\n");
return 1;
}
printf("%d\n", score);
return 0;
}実行手順
1. 作業ディレクトリに移動する
cd ~/Desktop2. コンパイルする
clang scanf_check.c -o scanf_check3. 実行する
./scanf_check実行例:
score: 80
80整数以外を入力した例:
score: abc
input errorコードの読み方
scanf(“%d”, &score) != 1
if (scanf("%d", &score) != 1) {
printf("input error\n");
return 1;
}%d は整数を1個読み取る指定です。
整数を正しく読み取れた場合、scanf は 1 を返します。
1 でない場合は、読み取りに失敗したと判断できます。
return 1
return 1;main 関数で 0 以外を返すと、プログラムが正常終了ではなかったことを表せます。
この例では、入力エラーが起きたため 1 を返しています。
点数の範囲を確認する
整数として読み取れても、値が適切とは限りません。
点数として扱うなら、たとえば 0 から 100 の範囲に制限できます。
#include <stdio.h>
int main(void) {
int score;
printf("score: ");
if (scanf("%d", &score) != 1) {
printf("input error\n");
return 1;
}
if (score < 0 || score > 100) {
printf("score must be between 0 and 100\n");
return 1;
}
printf("%d\n", score);
return 0;
}実行例:
score: 120
score must be between 0 and 100入力用の関数を作る
入力処理を関数に分けると、main 関数を読みやすくできます。
成功したら 1、失敗したら 0 を返す関数にします。
#include <stdio.h>
int input_score(int *score) {
printf("score: ");
if (scanf("%d", score) != 1) {
return 0;
}
if (*score < 0 || *score > 100) {
return 0;
}
return 1;
}
int main(void) {
int score;
if (input_score(&score) == 0) {
printf("input error\n");
return 1;
}
printf("%d\n", score);
return 0;
}int *score
int input_score(int *score)関数の中で入力結果を呼び出し元の変数に入れるため、ポインタで受け取っています。scanf に渡すときは、すでにアドレスを受け取っているため score と書きます。
scanf("%d", score)第20回の総合演習を改良する
ここからは、第20回の総合演習を入力エラーに対応した形にします。
改良版ソースコード
final_exercise_checked.c という名前で保存します。
#include <stdio.h>
struct Student {
char name[20];
int score;
};
int input_students(struct Student students[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("name: ");
if (scanf("%19s", students[i].name) != 1) {
return 0;
}
printf("score: ");
if (scanf("%d", &students[i].score) != 1) {
return 0;
}
if (students[i].score < 0 || students[i].score > 100) {
return 0;
}
}
return 1;
}
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;
}
int save_result(struct Student students[], int size, int sum, double average) {
FILE *fp;
int i;
fp = fopen("result.txt", "w");
if (fp == NULL) {
return 0;
}
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);
return 1;
}
int main(void) {
struct Student students[3];
int sum;
double average;
if (input_students(students, 3) == 0) {
printf("input error\n");
return 1;
}
print_students(students, 3);
sum = sum_scores(students, 3);
average = (double)sum / 3;
printf("sum: %d\n", sum);
printf("average: %.1f\n", average);
if (save_result(students, 3, sum, average) == 0) {
printf("file open error\n");
return 1;
}
return 0;
}改良版の実行手順
1. コンパイルする
clang final_exercise_checked.c -o final_exercise_checked2. 実行する
./final_exercise_checked正常な入力例:
name: Alice
score: 80
name: Bob
score: 90
name: Carol
score: 70
Alice 80
Bob 90
Carol 70
sum: 240
average: 80.0範囲外の点数を入力した例:
name: Alice
score: 120
input error改良した点
input_students の戻り値
int input_students(struct Student students[], int size)第20回では void でしたが、今回は int にしています。
入力に成功した場合は 1、失敗した場合は 0 を返します。
save_result の戻り値
int save_result(struct Student students[], int size, int sum, double average)ファイル保存も失敗する可能性があります。
そのため、保存に成功したら 1、失敗したら 0 を返す形にしています。
main 関数で失敗を確認する
if (input_students(students, 3) == 0) {
printf("input error\n");
return 1;
}関数の戻り値を確認し、失敗した場合は処理を中止します。
これにより、不正な入力のまま計算や保存を続けないようにできます。
初心者がつまずきやすい点
scanf の戻り値を入力値そのものだと思ってしまう
scanf の戻り値は、入力された値ではありません。
読み取りに成功した項目数です。
if (scanf("%d", &score) != 1)この例では、整数を1個読み取れたかどうかを確認しています。
文字列入力のサイズ制限を忘れる
次のように書くと、入力が長すぎる場合に危険です。
scanf("%s", students[i].name);name が20文字分の配列なら、次のように最大文字数を指定します。
scanf("%19s", students[i].name);最後の '\0' の分を残すため、20 ではなく 19 を指定します。
エラーが起きても処理を続けてしまう
入力に失敗した場合は、計算やファイル保存を続けないようにします。
失敗を検出したら、メッセージを表示して return 1; で終了するのが分かりやすい書き方です。
よくあるエラー
整数以外を入力すると処理が止まる
原因: %d で整数を読み取ろうとして失敗しています。
対処: 戻り値を確認し、失敗した場合はエラーメッセージを表示します。
長い名前を入力すると途中までしか保存されない
原因: %19s で最大19文字までに制限しているためです。
対処: この回では、配列サイズを超えないための制限として扱います。
input error だけでは原因が分かりにくい
原因: 入力失敗と範囲外を同じメッセージにしているためです。
対処: 必要なら、入力失敗と範囲外で別のメッセージを表示します。
練習用コード
範囲外メッセージを分ける
点数が範囲外の場合に、専用のメッセージを表示してみます。
if (students[i].score < 0 || students[i].score > 100) {
printf("score must be between 0 and 100\n");
return 0;
}人数を定数で管理する
3 を何度も書く代わりに、定数として管理できます。
#define STUDENT_COUNT 3使う側は次のように書きます。
struct Student students[STUDENT_COUNT];
input_students(students, STUDENT_COUNT);まとめ
今回のポイントは次のとおりです。
scanfは読み取りに成功した項目数を返すscanf("%d", &x) != 1で整数入力の失敗を確認できる- 入力値が正しい型でも、範囲チェックが必要な場合がある
- 関数の戻り値で成功と失敗を表せる
- 入力に失敗したら、その後の計算や保存を続けない
- 文字列入力では配列サイズを超えないように最大文字数を指定する
この回では、scanf のエラー処理を丁寧にする方法を学びました。
入力処理では、読み取りに成功したか、値が許可された範囲にあるかを確認することが重要です。
次回予告
次は、第20回の総合演習を複数ファイルに分割する流れを学びます。
入力、計算、保存の処理を別ファイルに分けることで、プログラムの構成をより整理できます。


