2015年11月15日日曜日

C++の数値入力で無限ループにはまった件

ちょっとC++の勉強をしてる時に入力処理で無限ループにはまる事態に陥ったのでその対応方法を書いとく

問題の処理:1~9までの数字を入力する際、不正入力されたとき再入力をさせる処理
// C++の再入力処理.
#include<iostream>
using namespace std;
int main() {

    int num;

    // 1~9が入力されていなければ再入力
    cout << "Please enter the numbers(1-9) --> ";
    while(1) {
        cin >> num;
        if(num > 0 && num < 10)break;
        cout << "Please enter it again.(1-9) -->";
    }

    return 0;
}
このとき、半角英数字「a」を入力すると.......
$ ./a.out
Please enter the numbers(1-9) --> a
Please enter it again.(1-9) -->Please enter it again.(1-9) -->Please enter it again.(1-9) -->Please enter it again.(1-9) -->
Please enter it again.(1-9) -->Please enter it again.(1-9) -->Please enter it again.(1-9) -->Please enter it again.(1-9) -->
(..以下略)
と無限ループした。

なん。。だと。。
ループするということは、while文が動いていて、「 cin >> num;」 を何度も通るはず。。そのたびに入力処理が走るはずだが。。。
原因と対策を調べてみた。


 原因
まぁどう考えてもcinの仕組みの理解不足だったw
当初、英数字はASCIIに従い、「'a'→ 97(0x61)」になると思ってたけど、そうじゃない
なんと型が違うと、エラーフラグが立って、入力値はcinのバッファに残り、再入力を受付けない仕組みになってた!

つまり、「a」を入力したときの流れは
  1. cin のバッファに「a」を格納
  2. cin のバッファ「a」をintに変換してnumに代入しようとする
  3. 型が合わないのでエラー。エラーフラグを立てる。
  4. numにはとりあえず「0」を入れる
  5. ループしてwhile文の先頭へ
  6. 「バッファにまだ入力値がある」または「エラーフラグが立っている」ので、再入力をスルー
  7. cin のバッファ「a」をnumに変換して代入しようとする
  8. 型が合わないのでエラー。エラーフラグを立てる。



という無限ループが完成する
はまってたのはコレ

ちなみに、手順4の「numにはとりあえず「0」を入れる」のも曲者で
もし、break条件にnum = 0が含まれてたら、不正値でも再入力ループを抜けてしまう
対策しないと0を入力する条件がある場合に困る

 対策
  1. 無限ループ対策
  2. 手順6で再入力させればいいので、
    「cinのバッファを消す」、「エラーフラグを落とす」をすればよい
    cinはistreamクラスを継承したオブジェクトなので、その辺のクラスを調べると丁度良いメソッドを見つけた
    ループするときにこのメソッドを呼んでやると再入力を受け付けてくれる
    メソッド名効果
    clearエラーフラグを落とす
    ignoreバッファを消す

  3. 「とりあえず「0」を入れる」対策
  4. これもちょうどいいメソッドがある
    メソッド名効果
    good変換が成功したか確認する
    変換が失敗すると0を返し、成功すると0以外を返す
    このメソッドをbreak文の条件に入れることで、num = 0が不正値の0か、入力値の0かを判断できる
 結果
以上より、最終的にこう書けばすべての入力に対して無敵!(修正行を色付にしている)
// C++の再入力処理.
#include<iostream>
using namespace std;
int main() {

    int num;

    // 1~9が入力されていなければ再入力
    cout << "Please enter the numbers(1-9) --> ";
    while(1) {
        cin >> num;
        // cin.good()が0のときは不正入力
        if( (cin.good()!=0) && num > 0 && num < 10) break;
        
        cin.clear();          // エラーフラグクリア
        cin.ignore(256,'\n'); // バッファクリア
        cout << "Please enter it again.(1-9) -->";
    }
    return 0;
}
一応、cin.ignore(256,'\n');の意味の説明
第一引数は消すバッファのByte数。256に意味はないけど、これくらいあれば全部消せそう
第二引数はバッファを消す終端文字を指す
C言語と同様に文字列の最後には'\n'が入るため、終端を'\n'で指定している
結果は
$ ./a.out
Please enter the numbers(1-9) --> a
Please enter it again.(1-9) -->あ
Please enter it again.(1-9) -->999999999999
Please enter it again.(1-9) -->abc nnn
Please enter it again.(1-9) -->aaaaaaaaaaaaaaaaaa
Please enter it again.(1-9) -->くぁwせdrftgyふじこlp
Please enter it again.(1-9) -->..
Please enter it again.(1-9) -->1
$
完成!!

0 件のコメント:

コメントを投稿