質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
char

charは文字データ型を指します。一文字分の文字コードの格納を想定としている型です。

標準入力

標準入力(stdin)は、プログラムが標準的に用いるデータ入力元。リダイレクトしない限り、プログラムを起動した端末のキーボードが標準入力になります。UNIX系OSやC言語に実装されて普及した概念ですが、他のOSや言語も含めた総称としても使われます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

2923閲覧

C++ char型の一文字入力がscanfでは上手くいかず、std::cinでは正常に動作する理由。

TAKAHIRO_24

総合スコア1

char

charは文字データ型を指します。一文字分の文字コードの格納を想定としている型です。

標準入力

標準入力(stdin)は、プログラムが標準的に用いるデータ入力元。リダイレクトしない限り、プログラムを起動した端末のキーボードが標準入力になります。UNIX系OSやC言語に実装されて普及した概念ですが、他のOSや言語も含めた総称としても使われます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2020/04/25 15:58

編集2020/04/25 16:13

前提

使用言語はC++です。
加減乗除の計算をする電卓プログラムを作成しているのですが、演算子を入力する際にscanfを使用すると正しい出力が行われず、std::cinでは正しい出力結果が出ます。その理由がわかりません。

プログラムの条件は以下の通りです。

数と記号 +, -, *, /, = のどれか1つを交互に入力します。
1行目は数であり、演算 +, -, *, / の優先順位(乗除算 *, / を加減算 +, - よりも先に計算すること)は考えず,入力の順序で計算し,= の行になったら,計算結果を出力します。

該当のソースコード

C++

1#include <stdio.h> 2 3int main(void) { 4 5 int a; 6 char cp; 7 int sum; 8 9 scanf("%d", &sum); 10 11 while (true) { 12 scanf("%c", &cp); // このscanfが正しく作動しません。 13 14 if (cp == '=') break; 15 16 scanf("%d", &a); 17 18 switch (cp) { 19 case '+': 20 sum += a; 21 break; 22 case '-': 23 sum -= a; 24 break; 25 case '*': 26 sum *= a; 27 break; 28 case '/': 29 sum /= a; 30 break; 31 } 32 } 33 34 printf("%d\n", sum); 35 36 return 0; 37}

発生している問題

変数cp入に力する際に上記のようにscanfを使用すると以下のように正しく出力されません。

1 + 2 = 1

試したこと

<iostream>をincludeして変数cpの入力をstd::cinで行うと正しく出力されました。

#include <stdio.h> #include <iostream> int main(void){ int a; char cp; int sum; scanf("%d", &sum); while(true){ std::cin >> cp; // 変更点 if(cp == '=') break; scanf("%d", &a); switch (cp) { case '+': sum += a; break; case '-': sum -= a; break; case '*': sum *= a; break; case '/': sum /= a; break; } } printf("%d\n", sum); return 0; }
1 + 2 = 3

補足情報

変数cpの入力以外のscanfはそのままでも正常に作動します。
そのためchar型でscanfを使用する際に何か注意点があるのかもしれないと考え調べましたがわかりませんでした。
なぜ変数cpの入力でscanfを使用したら正しく出力されないのか教えて頂けますでしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

自己解決のコメント欄

scanfで入力された文字は一度バッファに保存され、Enterキーを入力するとバッファをクリアし、再度そのメモリ領域が使用できる状態になります。

しかし、scanfでは最後の改行文字(\n)はクリアされずにバッファに残ってしまうため、上記のソースコードを例に挙げれば、変数sumで最後に入力した改行文字(\n)が変数cpに代入されてしまいます。

そのため、scanf(" %c", &cp)の空白はバッファに残った改行文字の入力分であると思われます。

バッファ(標準入力)まわりの話題というのはそうなんだけど、解釈はちょっと違います。

C++

1#include <cstdio> 2#include <iostream> 3 4int main(void) { 5 int a; 6 char cp; 7 int sum; 8 9 std::cout << "input a number > "; 10 std::cin >> sum; 11 cp=std::cin.get();//(A) 12 std::cout << (int)cp; 13 return 0; 14}

こうすると、10とか13とかの改行文字が(A)で取得出来ているのがわかるはずです。つまり、「scanfでは最後の改行文字(\n)はクリアされずにバッファに残ってしまう」= cinでは数値を入力したあと\nがクリアされる、という想定は間違っています。

scanf()だろうとcin << だろうと、どちらも「入力指示があったデータを可能な範囲で取り込み、変換出来なかったデータはバッファ(標準入力)に残る」という動作をしています。どちらも、数値データを取り込んだあと、改行文字に出会ったらそれは変換不可能なのでバッファに残して動作を終了します。(つまり、「Enterキーを入力するとバッファをクリアし」という動作も存在しません)

char cp;
scanf("%c",&cp);
は、問答無用に、バッファの先頭から一文字を取り込んでcpに格納しろ、という指示です。なので、バッファの先頭に改行文字があればそれを取り込んで処理を完了します。(これが期待しない動作になっていたわけですが)
ここで、書式指定を" %c"とすると、先頭の空白は0文字以上の空白文字にマッチします。空白文字というのは、空白、水平タブ、改行等、isspace()関数が真を返す文字たち。そうすると、改行は空白文字の一種なので、最初の空白に(あれば)マッチし、その後最初に出てくる空白文字ではない一文字が%cにマッチして取り込まれるということになります。ここはTAKAHIRO_24さんの仰るとおり。(でも、文字の前に空白文字が複数あっても" %c"の空白が全て引き受けてくれる、というのは認識してたかしら?)

cinだと期待の動きになったのは、cinでcharを取り込むときの動作が" %c"に相当するもの、と決められていたから、です。ちょっとずるいような回答になりますけど、そういうことなので。直前の整数取り込みがscanfなのかcin <<だったのかは関係ありません。

ちなみに、scanf()の書式指定"%d"とか"%s"とかも、バッファ先頭の0個以上の空白文字を読み飛ばす、ということになっています。だから、
scanf("%d",&a);
scanf("%d",&b);
などというのは、バッファに残っているEnterを読み飛ばして期待通りに動くわけです。
このついでに言えば、複数の整数を取り込むときに
scanf("%d %d",&a,&b);
とする人が時々いますけれど、実はこれ
scanf("%d%d",&a,&b);
と書いても動作は同じ。


ところで、質問のプログラムは(scanf版でも)"12+34-56=[Enter]"と連続して入力してもちゃんと動作する、というのはわかってましたか? (一応の理屈はこの回答中に書かれてますけど)

追記

scanf版で"1[Enter]+[Enter]2[Enter]=[Enter]"と入力したときの動作を、コンピュータになったつもりでトレースしてみましょう。
説明のため、一応プログラムに行番号をつけておきます。

C++

1 1: scanf("%d", &sum); 2 2: while(true){ 3 3: scanf("%c", &cp); 4 4: if(cp == '=') break; 5 5: scanf("%d", &a); 6 6: switch (cp) { 7 7: case '+': 8 8: sum += a; 9 9: break; 1010: case '-': 1111: sum -= a; 1212: break; 1313: case '*': 1414: sum *= a; 1515: break; 1616: case '/': 1717: sum /= a; 1818: break; 1919: } 2020: } 2121: printf("%d\n", sum);

これに、"1[Enter]+[Enter]2[Enter]=[Enter]"を喰わせます。
1:最初は標準入力は空なので入力待ち。"1[Enter]"を入力するのでsumに1を取り込みます。標準入力には\nが残っています。
2:のwhile(true)は素通しで
3:これまでの話で、cpには'\n'が入ります。標準入力は空になって
4:ifの条件は偽で次へ
5:ここでscanfは入力バッファが空なので入力待ちになります。ここに入ってくるデータは"+[Enter]"。"%d"は整数を要求します。'+'はもしかしたら整数の一部かもしれないので取り込みますが、次が'[Enter]'だと整数として解釈出来ないので、scanfは失敗。aの値は変更されないまま、また標準入力には"[Enter]"が入ってまま次に進みます。
6:cpには'\n'が入ってますから、どのcaseラベルともマッチしません。
20:switch文を抜けて、whileの終わりに到達。2:のwhileのところまで戻りましょう。
3:標準入力の先頭は'[Enter]'ですから、cpに'\n'を取り込みます。
4:cpは'\n'なので偽
5:標準入力が空で入力待ち、"2[Enter]が入力されますので、aには2が入って標準入力には[Enter]が残ります。
6:cpは'\n'なのでcaseにマッチなし
20:while終わり、2:に戻って
3:標準入力は'[Enter]'で'\n'をcpに取り込み
4:ifの条件は偽で次へ
5:標準入力は空なので入力待ち。"=[Enter]"が入力されますが、数値には変換不可で標準入力には"=[Enter]"が入った状態で次に
6:cpは'\n'なのでマッチなし
20:while終わり、2:に戻って
3:標準入力の先頭は'='なので、cpに取り込みます。標準入力には"[Enter]"が残っています(もう関係ないけど)
4:cpが'='なのでif文の判定式が真でbreak; 21:へ
21:さて、sumは...最初に設定された1から変更なし。なので1を表示します。
以上、終わり。

投稿2020/04/26 04:38

編集2020/04/26 11:49
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TAKAHIRO_24

2020/04/26 08:24 編集

thkanaさんの解答を読んだうえで私なりに考えを整理しながら以下に言葉を変えて表現してみました。間違いがあればご指摘いただければ幸いです。 --- ##### 整理 > scanf()だろうとcin << だろうと、どちらも「入力指示があったデータを可能な範囲で取り込み、変換出来なかったデータはバッファ(標準入力)に残る」という動作をしています。 > cinだと期待の動きになったのは、cinでcharを取り込むときの動作が" %c"に相当するもの、と決められていたから、です。 「入力 → バッファに保存」の動作はscanfとcinのどちらも同様に改行文字まですべて取り込むが、「バッファ → 変数へ代入(メモリ?)」の際の動作が異なる。 上記のソースコードで例えると、一度目の入力(変数sum)の際にどちらもバッファに改行文字を残して変数sumに数字のみを代入する。そのため、次のcpの入力の際にバッファには「\n+」のように改行文字と演算子が保存された状態になる。 「バッファ → 変数へ代入」の際は、scanfはバッファの先頭から順に読み取るため、演算子の前に改行文字に遭遇してしまい、変数cpに改行文字(\n)が代入されてしまった。一方、cinはバッファ内で最初に遭遇した%cに相当するもの(char型一文字)のみを変数cpへ格納したため期待通りの動作をした。 ##### 疑問点 二度目の変数cpを入力する際に「=」が正しく認識されて、プログラムが終了した理由がわかりません。 > scanf()の書式指定"%d"とか"%s"とかも、バッファ先頭の0個以上の空白文字を読み飛ばす、ということになっています。 とのことなので、ソースコードを例にすると「1+2=1」は sum ← 1  // バッファには「\n」が残る cp ← + // 実際は「\n」が代入   バッファには「+」が残る a ← 2   // バッファには「\n」が残る cp ← = // 実際は「\n」が代入 バッファには「=」が残る という動作になっているのでしょうか。しかし「=」を入力した段階でプログラムは終了し、計算結果が返ってくるので `if (cp == '=') break;` が実行されていると思うのですが、私の認識ですとcpには「=」ではなく「\n」が代入されているはずですので、プログラムは終了しないのではないかと思ってしまいます。 どこの認識が間違っているのでしょうか。 > 文字の前に空白文字が複数あっても" %c"の空白が全て引き受けてくれる ご指摘いただくまで認識していなかったので試してみたところ確かにそのように動作しました。 しかしその理由がわかりません。scanfはバッファの先頭から読み取るということなのでそのまま解釈すれば空白文字は一文字分だけ読み飛ばすと考えてしまいます。 > ところで、質問のプログラムは(scanf版でも)"12+34-56=[Enter]"と連続して入力してもちゃんと動作する、というのはわかってましたか? 完全な推測になってしまいますが、改行文字や空白を入れずに入力しているためプログラムがそれぞれの変数のデータ型と照らし合わせて判断しているためでしょうか。
thkana

2020/04/26 11:49

プログラムの動作については、クソ真面目に一つ一つ入力と命令を追いかけてみればわかります。「わからない」というのはそれをやっていなから...だと私は思います。回答本文にトレースした例を追記しますので、確認して下さい。面倒な作業ではありますが、難しくはないはず。 > しかしその理由がわかりません。scanfはバッファの先頭から読み取るということなのでそのまま解釈すれば空白文字は一文字分だけ読み飛ばすと考えてしまいます。 理由なんかありません。そういう規則、C言語の規格決定者がそういう動作をすることに決めたからです。受け入れるしかないです。 > プログラムがそれぞれの変数のデータ型と照らし合わせて判断 まぁそういう言い方もありかも知れませんが... 数値の入力は「数値に変換出来る文字を見つけてから、数値に変換出来ない文字に出会うまで」行われます。変換できない文字がEnter('\n')だろうと+だろうと関係ありません。そして、変換出来なかった文字はバッファに残るのですから、それが'+'なら次の一文字入力で拾われます。なんなら、先程と同様に、プログラムの動作を逐一追ってみれば「推測」なんかではなく、そのようにしか動かないということがわかるかと思います。
TAKAHIRO_24

2020/04/26 14:49

追記までしていただいてありがとうございます。 キーボードからの一度目の+の入力はすでに変数cpを入力するscanfは終了していて、次の変数aを入力するscanfで+を入力していたのですね。 また、変数aを入力する際に+[Enter]と=[Enter]の挙動を同じだと考えていましたが、+[Enter]の場合は整数の正負を判断するために+は読み込まれるのですね。勘違いしていましたが追記を確認して理解しました。 詳細まで解説いただきありがとうございます。 大変学びにつながったのでベストアンサーをこちらに変更しておきたいと思います。
thkana

2020/04/26 22:05

> +[Enter]の場合は整数の正負を判断するために+は読み込まれるのですね まるで予め知っていたかのように書いていますが、実は自分でも --- scanf("%c", &cp); // このscanfが正しく作動しません。 if (cp == '=') break; printf(":cp/%d\n", cp); scanf("%d", &a); printf(":a/%d\n",a); --- とか入れて情報を拾って、プログラムの動作を真面目に追いかけてみて気がついたことだったのは秘密です。
TAKAHIRO_24

2020/04/27 11:17

そうだったのですね。 > プログラムの動作については、クソ真面目に一つ一つ入力と命令を追いかけてみればわかります。「わからない」というのはそれをやっていなから...だと私は思います。 この言葉が身に沁みます。 この度は最後までありがとうございました。機会があればまたよろしくお願いします。
guest

0

scanf(" %c", &cp)std::cin >> cp と同じ。
scanf("%c", &cp)std::cin >> std::noskipws >> cp >> std::skipws と同じ。

skipws や noskipws は、「空白(white space) を読み飛ばす(skip)」かどうかという
cin の内部状態を変更するマニピュレータです。

投稿2020/04/27 01:23

kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TAKAHIRO_24

2020/04/27 11:21

std::noskipwsは初めて知りました。 CとC++の対応付けありがとうございます。参考にします。
guest

0

改行コード(\n)が変数cpに入力されているようです。
そのため以下のように%cpの前に空白をいれると正しく出力されました。

C++

1scanf(" %cp", &cp);

投稿2020/04/25 16:11

TAKAHIRO_24

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2020/04/25 22:03

なぜそうすると期待の動作になるのか、というリクツは大丈夫ですか?
TAKAHIRO_24

2020/04/26 02:40 編集

以下の認識で間違いないでしょうか。 scanfで入力された文字は一度バッファに保存され、Enterキーを入力するとバッファをクリアし、再度そのメモリ領域が使用できる状態になります。 しかし、scanfでは最後の改行文字(\n)はクリアされずにバッファに残ってしまうため、上記のソースコードを例に挙げれば、変数sumで最後に入力した改行文字(\n)が変数cpに代入されてしまいます。 そのため、scanf(" %c", &cp)の空白はバッファに残った改行文字の入力分であると思われます。
thkana

2020/04/26 04:34

結構長くなったので別回答に書きます ところで、回答中の" %cp", &cpは" %c", &cpですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問