【C/C++】scanfでループしたので標準入力から数値を受け取ることについてまとめる
scanfでループする
以下のコードは不正な入力(例えばabc)を与えると無限ループします.
1/* scanfに不正入力をするとループするテスト*/
2#include <stdio.h>
3#include <stdlib.h>
4
5int main(void){
6 int x = 0;
7 while(true){
8 printf("Please input x\n");
9 scanf("%d", &x);
10 printf("x = %d\n", x);
11 if(x == -1) break;
12 }
13 return EXIT_SUCCESS;
14}
この原因はscanfが予期しないデータの入力によって,
バッファのデータをそのまま残して動作を終了していることにあります.
scanfは予期しない入力があると無限ループに陥る(C学習中) - 虎塚
変換指定文字列で、期待していなかったデータを入力すると、 バッファのデータをそのまま残し、動作を終了してしまいます。
1fflush(stdin);
1scanf("%*c");
このあたりも試してバッファのクリアを狙ってみましたが,思うような動作には至りませんでした.
fgetsのあとatoiする
1// 独自関数fgetiを実装する
2// ほぼ問題ないがatoiが範囲外の処理ができない
3#include<stdio.h>
4#include<stdlib.h>
5#include<string.h>
6#include<limits.h>
7#define N 512
8
9int fgeti(int*, FILE*);
10
11int main()
12{
13 int x;
14 while(true)
15 {
16 printf("Please input a number\n");
17 if (fgeti(&x, stdin) == EOF)
18 {
19 continue;
20 }
21 printf("x = %d\n", x);
22 }
23}
24
25int fgeti(int* p, FILE *fp)
26{
27 char buf[N];
28 if (fgets(buf, N, fp) == NULL)
29 {
30 return EOF;
31 }
32 *p = atoi(buf);
33 if(*p == 0 && (strcmp(buf, "0\n")!=0))
34 {
35 return EOF;
36 }
37 return 1;
38}
こうすることで大抵の処理には耐えますが,範囲(INT_MIN〜INT_MAX)外と
123abcのような頭に数字のついた入力に耐えません.
文字列から数値への変換 - forest book
なお,atoiは変換できない文字列の際に0を返すことにも注意です.
【C言語】atoi関数|ato関数群(atoi, atol, atoll, atof)完全解説 | MaryCore
fgetsのあとstrtolする
1atoiよりエラーに強いstrtolを使って
2strtoiという関数を定義しました.
3
4strtolは第二引数のendptrに変換出来ない文字列を格納します.
5[C言語関数辞典 - strtol](http://www.c-tipsref.com/reference/stdlib/strtol.html)
6
7
8// 独自関数fgetiを実装する// atoiからstrtoi(自作)に変更
9// これでどんな入力にも対応できる...はず
10#include<stdio.h>
11#include<stdlib.h>
12#include<string.h>
13#include<limits.h>
14#define N 512
15
16int fgeti(int*, FILE*);
17int strtoi(const char*);
18
19int main()
20{
21 int x;
22 while(true)
23 {
24 printf("Please input a number\n");
25 if (fgeti(&x, stdin) == EOF)
26 {
27 continue;
28 }
29 printf("x = %d\n", x);
30 }
31}
32
33int fgeti(int* p, FILE *fp)
34{
35 char buf[N];
36 if (fgets(buf, N, fp) == NULL)
37 {
38 return EOF;
39 }
40 *p = strtoi(buf);
41 if(*p == 0 && (strcmp(buf, "0\n")!=0))
42 {
43 return EOF;
44 }
45 return 1;
46}
47
48int strtoi(const char* str)
49{
50 char* endptr;
51 long lstr = strtol(str, &endptr, 10);
52 if (*endptr != '\0' || lstr INT_MIN || INT_MAX < lstr)
53 {
54 return 0;
55 }
56 return (int)lstr;
57}
少なくともscanfで直接受け取るよりは堅牢かと思います.