- ベストアンサー
scanfの入力をgets関数で読み捨てることについて
-------------------------------------- #include<stdio.h> int main(void) { double dt=0.0,sum=0.0; char ss[80]; int ret; ret=scanf("%lf",&dt); puts(""); if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } while(dt!=999){ sum=sum+dt; ret=scanf("%lf",&dt); puts(""); if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } } printf("合計=%f\n",sum); return 0; } -------------------------------------- 以上のプログラムで、入力した数値の合計を出し「999」が入力されたら終了させ、数値以外が入力されたら、gets関数で読み捨て入力を続けていくということをしたいのですが、例えば、 ◎1----------- 2 3 4 999 合計=9.000000 --------------- ◎1のように数値のみだと正しく表示されます。 次に、 ◎2-------------------- a 整数を入力してください b 整数を入力してください 2 3 999 合計=5.000000 ------------------------ ◎2のように数値以外を先に入力し、その後に数値を入力しても正しく表示されます。 次に、 ◎3------------------- 2 3 a 数値を入力してください b 数値を入力してください 999 合計=11.000000 ----------------------- ◎3のように数値を入力した後に、数値以外を入力したら正しく表示されません。 次に、 ◎4-------------------- 2 a 整数を入力してください b 整数を入力してください 3 999 合計=9.000000 ------------------------ ◎4のように数値をまず入力しその後、数値以外を入力する。その後、数値を入力して終了させても、合計値が正しく表示されません。 まだ、バッファについて完全に理解していないということもあり、何故こうなってしまうのか分かりません。 教えていただけると嬉しいです。
- みんなの回答 (15)
- 専門家の回答
質問者が選んだベストアンサー
>「a」、「b」を入力した時点で、「3」はバッファ内でどうなっているのでしょうか? バッファ内には何もありません。入力バッファはgets()で捨てられています。 >正常な入力が次に行われるまで、正常に入力された値はバッファ内に残っているということでしょうか? バッファ内ではなく「dtそのもの」に「前回の値」が入ったままになっています。 実は、2も「正しく動いてないが、偶然、結果が正しい値と一致しただけ」です。 scanfは「書式と一致した入力を受け取ったら指定された変数に値を代入して、書式に一致しない入力を受け取った時点で処理を中断し、それまでに代入出来た個数を返す」と言う仕様になっています。 ◎2は double dt=0.0,sum=0.0; の初期化で「dtが0.0になっている状態」で、最初の ret=scanf("%lf",&dt); の行で「a」が入力され「dtは変更されずに0.0のまま」で if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } が実行され、バッファ「だけ」が消去されます。しかし「dtは前のまま」です。そして「aが入力され、dtが何になっているか判らないというのに」以下の while(dt!=999){ sum=sum+dt; の部分で「値が何なのか判らないdtをsumに足している」のです。 ですが「運良く、dtを0.0に初期化したまま」だったので、sumの値は変わりません。 ◎3は double dt=0.0,sum=0.0; の初期化で「dtが0.0になっている状態」で、最初の ret=scanf("%lf",&dt); で「2」が入力され、dtは2.0になります。retは1になりますから if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } は実行しません。そして while(dt!=999){ sum=sum+dt; でsumにdtが足され、sumが2.0になります。次に ret=scanf("%lf",&dt); で「3」が入力され、dtが3.0になり、retは1になります。次の if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } は実行されません。ここでwhileループを繰り返しますから while(dt!=999){ sum=sum+dt; でsumにdtが足され、sumが5.0になります。次に ret=scanf("%lf",&dt); で「a」が入力され、dtは3.0のまま、変更されません。入力は失敗してますからretは0になりますので if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } が実行されます。そして、そのままwhileループを繰り返し while(dt!=999){ sum=sum+dt; でsumにdtが足され、sumが8.0になります。「a」を入力したのでsumにdtを足してはいけないのに、足してしまっています。続いて「b」を入力した時も「dtが3.0のまま、sumにdtを足す」ので「sumが11.0」になります。 結果として「999を入力して終了させると、sumが11.0になっている」ことになります。 入力の前と後で、dtとsumの値が何になっているか、まとめてみます。 ◎1の場合 入力「2」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「2.0」 dtを加算後のsum「2.0」 入力「3」 入力前のdt「2.0」 入力前のsum「2.0」 入力後のdt「3.0」 dtを加算後のsum「5.0」 入力「4」 入力前のdt「3.0」 入力前のsum「5.0」 入力後のdt「4.0」 dtを加算後のsum「9.0」 入力「999」 sum「9.0」を表示 ◎2の場合 入力「a」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「0.0」 dtを加算後のsum「0.0」 入力「b」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「0.0」 dtを加算後のsum「0.0」 入力「2」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「2.0」 dtを加算後のsum「2.0」 入力「3」 入力前のdt「2.0」 入力前のsum「2.0」 入力後のdt「3.0」 dtを加算後のsum「5.0」 入力「999」 sum「5.0」を表示 「a」「b」入力時はsumにdtを加算してはいけないが「偶然、dtが0.0のまま」なので、影響が出なかった。影響は出なかったが、バグはバグ。 ◎3の場合 入力「2」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「2.0」 dtを加算後のsum「2.0」 入力「3」 入力前のdt「2.0」 入力前のsum「2.0」 入力後のdt「3.0」 dtを加算後のsum「5.0」 入力「a」 入力前のdt「3.0」 入力前のsum「5.0」 入力後のdt「3.0」 dtを加算後のsum「8.0」 入力「b」 入力前のdt「3.0」 入力前のsum「8.0」 入力後のdt「3.0」 dtを加算後のsum「11.0」 入力「999」 sum「11.0」を表示 ここで「バグの影響」がモロに出ています。 ◎4 入力「2」 入力前のdt「0.0」 入力前のsum「0.0」 入力後のdt「2.0」 dtを加算後のsum「2.0」 入力「a」 入力前のdt「2.0」 入力前のsum「2.0」 入力後のdt「2.0」 dtを加算後のsum「4.0」 入力「b」 入力前のdt「2.0」 入力前のsum「4.0」 入力後のdt「2.0」 dtを加算後のsum「6.0」 入力「3」 入力前のdt「2.0」 入力前のsum「6.0」 入力後のdt「3.0」 dtを加算後のsum「9.0」 入力「999」 sum「9.0」を表示 ここも「バグの影響」がモロに出ています。 結論は「数値以外を入力した時も、sumにdtを足しているのが悪い」のです。 バッファがどうとか、そういう問題ではありません。 以下のように修正しましょう。 #include<stdio.h> int main(void) { double dt=0.0,sum=0.0; char ss[80]; int ret; ret=scanf("%lf",&dt); puts(""); if(ret==1){ sum=sum+dt; //入力が正常な時だけsumに足す } else if(ret!=EOF){ gets(ss); printf("数値を入力してください\n"); puts(""); } while((ret!=EOF)&&(dt!=999)){ ret=scanf("%lf",&dt); puts(""); if(ret==1){ sum=sum+dt; //入力が正常な時だけsumに足す } else if(ret!=EOF){ gets(ss); printf("数値を入力してください\n"); puts(""); } } printf("合計=%f\n",sum); return 0; } あと、蛇足ですが「数字の後にEOF(Ctrl+Z)を入力したら、プログラムが永久に止まらなくなる」と言うのも修正してあります。 修正前のプログラムは 2<Enter> 3<Ctrl+Z><Enter> と入力したら「止まらなくなる」ので絶対にやってはいけません。
その他の回答 (14)
- asuncion
- ベストアンサー率33% (2127/6289)
>実際に業務で動くCプログラムを書いたことがないもので、 >間違ったことを書いてしまいました。 とはいうものの、今回の場合、 インタラクティブな環境で動けばじゅうぶん (ファイルをリダイレクトすることはあまり想定してなさそう)かな、 という感じがしますので、 rewind(stdin); で動くんだったらそれでもいいんじゃないかとも思います。
- chie65536(@chie65535)
- ベストアンサー率44% (8740/19838)
>後、入力で「45abcd」とした場合、%lfで「45」だけを読み取り、 >バッファに残された「abcd」の判定の時点でエラー表示する。 >というのは、「45abcd」と入力した時点で、「45」を読み込まず、 >エラー表示するということは出来ないのですかね? できますよ。 scanfが1を返して来たら(retが1になっていたら)、次の1文字をgetchar()で拾ってみて、拾った文字が「'\n'またはEOF」ならOK、そうじゃないなら、続きに変な文字がある、って判ります。 入力が「45abcd」なら「scanfが1を返して、その後でgetcharすると、getcharが'a'を返します。 その場合は「scanfが1じゃない値を返した時」と同じ処理をしましょう。 なお「12 34」と入力した場合、1回目のscanfは「12」を受け取り、2回目のscanfは「キーボード入力をしてないのにも関わらず」続きの「34」を受け取ってしまいます。 つまり「1行の入力で、2回以上のscanfが行われてしまう」ので、注意が必要です。 例えば「1 2 3 4 999」と「1行で、スペースで区切って入力」すると、一気に計算して「合計=10.000000」って表示が出て終了してしまいます。 これも、前述の「scanfが1を返して来たら(retが1になっていたら)、次の1文字をgetchar()で拾ってみて、拾った文字が「'\n'またはEOF」ならOK、そうじゃないなら、続きに何かの文字がある」と言う判定で回避できます。
- asuncion
- ベストアンサー率33% (2127/6289)
>今の時代、誰も「入力バッファを消去するつもりで、 >rewind(stdin)と書いてはいけない」って、教えてくれないんですねえ。 実際に業務で動くCプログラムを書いたことがないもので、 間違ったことを書いてしまいました。
- chie65536(@chie65535)
- ベストアンサー率44% (8740/19838)
蛇足な追記。 >まあおそらく、 >rewind(stdin); >でじゅうぶんではないかと思いますけれど。 充分ではありません。重大なバグを生みます。 >お言葉ですが、rewindはれっきとした標準関数に属します。 確かに「標準関数」に属し「rewind(stdin)」の動作も定義されています。 >今回の仕様を満たすために使用することは、全く問題ありません。 問題大有りです。 「rewind(stdin)」は「標準入力ストリームのファイルポインタを先頭に戻す」と定義されています。 誰も「入力バッファを消去するつもりで、rewind(stdin)と書いた時、標準入力がリダイレクトされていた時の動作」を試してないんでしょうか? 1<改行> 2<改行> a<改行> b<改行> <EOF> と書かれたテキストファイルを、バッファを消去するつもりで、rewind(stdin)と書いたプログラムに入力リダイレクトしたら、どうなると思います? 「1」をscanfして、「2」をscanfして、「a」をscanfした瞬間、rewind(stdin)され、ファイルポインタが1行目に戻ります。 ファイルポインタが1行目に戻れば、「1」をscanfして、「2」をscanfして、「a」をscanfした瞬間、rewind(stdin)され、ファイルポインタが1行目に戻ります。 ファイルポインタが1行目に戻れば、「1」をscanfして、「2」をscanfして、「a」をscanfした瞬間、rewind(stdin)され、ファイルポインタが1行目に戻ります。 ファイルポインタが1行目に戻れば、「1」をscanfして、「2」をscanfして、「a」をscanfした瞬間、rewind(stdin)され、ファイルポインタが1行目に戻ります。 ファイルポインタが1行目に戻れば、「1」をscanfして、「2」をscanfして、「a」をscanfした瞬間、rewind(stdin)され、ファイルポインタが1行目に戻ります。 以下、永久に繰り返し。 今の時代、誰も「入力バッファを消去するつもりで、rewind(stdin)と書いてはいけない」って、教えてくれないんですねえ。困ったもんだ。
- chie65536(@chie65535)
- ベストアンサー率44% (8740/19838)
うっかりミス。 #include<stdio.h> int main(void) { double dt=0.0,sum=0.0; char ss[80]; int ret; ret=scanf("%lf",&dt); puts(""); if(ret==1){ if(dt!=999) sum=sum+dt; //入力が正常で999でない時だけsumに足す } else if(ret!=EOF){ gets(ss); printf("数値を入力してください\n"); puts(""); } while((ret!=EOF)&&(dt!=999)){ ret=scanf("%lf",&dt); puts(""); if(ret==1){ if(dt!=999) sum=sum+dt; //入力が正常で999でない時だけsumに足す } else if(ret!=EOF){ gets(ss); printf("数値を入力してください\n"); puts(""); } } printf("合計=%f\n",sum); return 0; } 999を入力した時はsumに足しちゃ駄目ですね。ウッカリミスです。
- redfox63
- ベストアンサー率71% (1325/1856)
2を入力 dt:2.0 sum:0.0 whileループ開始 sum = 2.0 ... sum = 0.0 + 2.0 // ここの加算は想定通り 3の入力で dtが3.0に更新 if文の条件不成立のためループの先頭へ sum = 5.0 ... sum = 2.0 + 3.0 // ここの加算は想定通り aの入力 dtは3.0のまま if文の条件成立のため文字列出力等の処理 ループの先頭へ sum = 8.0 ... sum = 5.0 + 3.0 // ここの加算は想定外 bの入力dtは3.0のまま if文の条件成立のため文字列出力等の処理 ループの先頭へ sum = 11.0 ... sum 8.0 + 3.0 // ここの加算は想定外 999の入力 dtが999.0に更新 if文の条件不成立のためループの先頭へ while文が不成立のためループを抜ける つまりscanfが失敗した場合は 引数で与えられた変数への代入は行われないため 前回成功した値のままである点が考慮されていないのです scanfに残ったバッファとは直接関係のない部分でバグっていますよ while( dt != 999.0 ) { sum += dt; dt = 0.0; ret = scanf( "%lf", %dt ) といった具合にしてもいいでしょう
お礼
ご回答ありがとうございます。 >aの入力 dtは3.0のまま >bの入力dtは3.0のまま >つまりscanfが失敗した場合は 引数で与えられた変数への代入は行われない >ため 前回成功した値のままである点が考慮されていないのです 以上の内容理解できました! デバッグのステップオーバーという機能を勉強して、まだ完璧ではないですが、リアルタイムでdt等の値を見て理解が深まりました。
- yaemon_2006
- ベストアンサー率22% (50/220)
>"rewind"に"stdin"を渡したときの動作は未定義。 ごめんなさい。"fflush"と間違ってた。
- yaemon_2006
- ベストアンサー率22% (50/220)
"rewined" -> "rewind"
- yaemon_2006
- ベストアンサー率22% (50/220)
"rewined"に"stdin"を渡したときの動作は未定義。
- asuncion
- ベストアンサー率33% (2127/6289)
質問者さんの環境が rewind(stdin); を使えないような特殊なものだったら、 #3さんのとおりになさってみてください。それでいまくいけば万々歳ですね。 まあおそらく、 rewind(stdin); でじゅうぶんではないかと思いますけれど。
- 1
- 2
お礼
ご回答ありがとうございます。 各実行結果のdtの値とsumの値を詳細にご回答していただき、かなり理解できました! 修正してもらったプログラムも理解できました! 修正してもらったプログラムで、 ------------------------------------------ if(ret= =1){ sum=sum+dt; //入力が正常な時だけsumに足す } else if(ret!=EOF){ gets(ss); printf("数値を入力してください\n"); puts(""); } ------------------------------------------ 最初のifでretで「1」を判定しているので、次のelse ifで「ret= =0」としても、問題ないですかね? 後、入力で「45abcd」とした場合、%lfで「45」だけを読み取り、バッファに残された「abcd」の判定の時点でエラー表示する。というのは、「45abcd」と入力した時点で、「45」を読み込まず、エラー表示するということは出来ないのですかね? お答えいただければ嬉しいです。