みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

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のように数値をまず入力しその後、数値以外を入力する。その後、数値を入力して終了させても、合計値が正しく表示されません。

まだ、バッファについて完全に理解していないということもあり、何故こうなってしまうのか分かりません。
教えていただけると嬉しいです。

投稿日時 - 2009-04-02 16:59:56

QNo.4847116

困ってます

質問者が選んだベストアンサー

>「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>

と入力したら「止まらなくなる」ので絶対にやってはいけません。

投稿日時 - 2009-04-03 11:05:32

お礼

ご回答ありがとうございます。

各実行結果の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」を読み込まず、エラー表示するということは出来ないのですかね?

お答えいただければ嬉しいです。

投稿日時 - 2009-04-03 15:04:40

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(15)

ANo.15

>実際に業務で動くCプログラムを書いたことがないもので、
>間違ったことを書いてしまいました。

とはいうものの、今回の場合、
インタラクティブな環境で動けばじゅうぶん
(ファイルをリダイレクトすることはあまり想定してなさそう)かな、
という感じがしますので、
rewind(stdin);
で動くんだったらそれでもいいんじゃないかとも思います。

投稿日時 - 2009-04-03 20:28:05

ANo.14

>後、入力で「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、そうじゃないなら、続きに何かの文字がある」と言う判定で回避できます。

投稿日時 - 2009-04-03 17:54:15

ANo.13

>今の時代、誰も「入力バッファを消去するつもりで、
>rewind(stdin)と書いてはいけない」って、教えてくれないんですねえ。

実際に業務で動くCプログラムを書いたことがないもので、
間違ったことを書いてしまいました。

投稿日時 - 2009-04-03 13:47:03

ANo.12

蛇足な追記。

>まあおそらく、
>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)と書いてはいけない」って、教えてくれないんですねえ。困ったもんだ。

投稿日時 - 2009-04-03 11:30:42

ANo.11

うっかりミス。

#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に足しちゃ駄目ですね。ウッカリミスです。

投稿日時 - 2009-04-03 11:14:56

ANo.9

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 )

といった具合にしてもいいでしょう

投稿日時 - 2009-04-03 05:05:03

お礼

ご回答ありがとうございます。

>aの入力 dtは3.0のまま
>bの入力dtは3.0のまま
>つまりscanfが失敗した場合は 引数で与えられた変数への代入は行われない
>ため 前回成功した値のままである点が考慮されていないのです

以上の内容理解できました!
デバッグのステップオーバーという機能を勉強して、まだ完璧ではないですが、リアルタイムでdt等の値を見て理解が深まりました。

投稿日時 - 2009-04-03 14:36:13

ANo.8

 
>"rewind"に"stdin"を渡したときの動作は未定義。

 ごめんなさい。"fflush"と間違ってた。
 

投稿日時 - 2009-04-03 00:46:36

ANo.7

 
 "rewined" -> "rewind"
 

投稿日時 - 2009-04-03 00:41:51

ANo.6

 
 "rewined"に"stdin"を渡したときの動作は未定義。
 

投稿日時 - 2009-04-03 00:38:30

ANo.5

質問者さんの環境が
rewind(stdin);
を使えないような特殊なものだったら、
#3さんのとおりになさってみてください。それでいまくいけば万々歳ですね。
まあおそらく、
rewind(stdin);
でじゅうぶんではないかと思いますけれど。

投稿日時 - 2009-04-02 23:28:47

ANo.4

#3さん
>処理系が不明なので rewind(stdin); は不可>#2

お言葉ですが、rewindはれっきとした標準関数に属します。
今回の仕様を満たすために使用することは、全く問題ありません。

投稿日時 - 2009-04-02 23:16:18

ANo.3

処理系が不明なので rewind(stdin); は不可>#2. 代わりに int ch; を定義してから
while ((ch = getchar()) != '\n' && ch != EOF);
くらい.

投稿日時 - 2009-04-02 23:09:06

ANo.2

scanfの入力(結果)をgetsで読み捨てる、というのが
どういうことを指しているのか、どうもピンと来ません。

#include <stdio.h>
int main(void)
{
double dt, sum = 0;

while (1) {
printf("数値を入力してください(999で終了)\n");
if (scanf("%lf", &dt) == 1) {
if (dt != 999) {
sum += dt;
}
else {
break;
}
}
else {
printf("数値以外を入力しましたね。\n");
rewind(stdin);
}
}
printf("合計=%f\n", sum);
return 0;
}

投稿日時 - 2009-04-02 20:46:01

お礼

ご回答ありがとうございます。

rewind関数を参考書で見てみたのですが、初心者なため、どうも使い方が理解できませんでした。すいません。

----------------------------
#include<stdio.h>
int main()
{
int n=0;
int ret;
char ss[80];

while(n==0){
printf("n=");
ret=scanf("%d",&n);
if(ret!=1){
gets(ss);
}
}
return 0;
}
----------------------------
以上のプログラムが参考書にあり、整数以外の入力があった場合は、バッファ内から消す?(参考書にはバッファ内に残したデータを読み捨てると書いてありました)というものでした。
これを、数値の連続加算に応用しようと思いました。

投稿日時 - 2009-04-03 00:20:23

ANo.1

加算処理の方法がまずいためでしょう

while( dt!=999 ) {
  // ここで加算処理をしているが
  // 文字など数値以外が入力された場合にも
  // 前回の正常に得られたdtを加算している
  sum += dt;
  ret = scanf( "%lf", &dt )
  if ( ret != 1 ) {
    gets(ss);
    printf("数値を入力してください\n");
    puts("");
    // ここで失敗したのだからdtをリセットする
    dt = 0.0;
  }
}

投稿日時 - 2009-04-02 17:29:37

お礼

ご回答ありがとうございます。

>前回の正常に得られたdtを加算している

以上のご回答というようになっていると分かりました。
例えば、実行結果◎3で「a」、「b」を入力しgetsでバッファ内から消しても、「3」はバッファに残っているということですかね?
「a」、「b」を入力した時点で、「3」はバッファ内でどうなっているのでしょうか?
正常な入力が次に行われるまで、正常に入力された値はバッファ内に残っているということでしょうか?

重ね重ね質問になってしまいましたが、ご回答いただけると嬉しいです。

投稿日時 - 2009-04-03 00:07:37