C++はどうやってコンソールとやり取りするか
C++はコンソールI/Oを <iostream> ヘッダーのストリームを通じて行います。cout(文字出力)で外の世界へ書き出し、cin(文字入力)でユーザーから読み込みます。これらは、すでにビットシフトとして見たことのある2つの演算子を、ここでは用途を変えて使います。
<<は挿入演算子です。データをcoutの中へ送り込みます。>>は抽出演算子です。データをcinの中から変数へ引き出します。
向きを覚える便利な方法: 矢印はデータが流れる向きを指しています。テキストを文字列に保持できるようになった今、そのテキストをプログラムへ出し入れしてみましょう。
coutで出力する
cout は、挿入したものすべてを標準出力へ送ります。1つの文の中で複数の << を連結し、テキスト・数値・変数を自由に混ぜられます。
各 << は、改行を挿入するまで同じ行に追加されていきます。それには2つの方法があります。'\n' は改行文字を挿入し、endl は改行を挿入し、さらにバッファを画面へフラッシュします。フラッシュには実際のコストがあるので、何千行も出力するループの中では '\n' を選びましょう。ストリームは必要なときに自分でフラッシュします(そしてプログラム終了時には必ず)。
// 1回限りのメッセージなら問題ありません:
cout << "Done" << endl;
// ホットなループでは、こちらを - 反復ごとの強制フラッシュなし:
for (int i = 0; i < 1000000; ++i)
cout << i << '\n';
cinと>>で読み込む
cin >> variable は、空白で区切られたトークンを1つ読み取り、それを変数の型に変換します。先頭の空白や改行はスキップし、次の空白で止まります。
>> は空白で止まるため、個々の数値や単語1つには最適ですが、文全体には使えません。入力 hello world に対する cin >> word は hello だけを読み取り、world は次の読み込みのためにバッファに残します。
getlineで1行全体を読み込む
1行全体を — スペースも含めて — 取り込むには getline(cin, line) を使います。これはEnterキーまでのすべてを std::string に読み込みます。
入力にスペースが含まれうるとき — 名前、住所、文章 — にはこれが正しい道具です。ただし、getline を >> と混ぜた瞬間にあなたを待ち受ける落とし穴が1つだけあります。
cin + getline の改行の落とし穴
これはC++で群を抜いて多いI/Oバグです。cin >> n を実行すると、抽出は数値を読み取りますが、改行(あなたが押したEnterキー)を入力バッファに残します。次の getline はその残った改行をすぐに見つけ、行をすでに終わったものとして扱い、空文字列を渡してきます — 入力を待って止まることなく。
int age;
string city;
cin >> age; // 30 と入力してEnterを押す。'\n' がバッファに残る
getline(cin, city); // 残った '\n' を読み取る -> city は "" になる(空!)
修正方法は、その残った改行を >> の後、getline の前に cin.ignore で捨てることです。
cin.ignore(numeric_limits<streamsize>::max(), '\n') は、改行を1つ食べる(または入力の終端に達する)までの文字をスキップします。これが堅牢な版です。短い cin.ignore() は1文字しか捨てないため、ユーザーが数値の後に余分なスペースを打つと壊れます。習慣として完全な形を使いましょう。
入力が失敗したとき
数値を期待していた場所にユーザーが文字を打つと、抽出は失敗します。cin はエラー状態に入り、対象の変数は変更されないまま残ります(C++11以降は 0 に設定されます)。さらに悪いことに、cin が一度失敗状態になると、それ以降のすべての読み込みも黙ってスキップされるため、無限ループに陥ることがあります。
回復は常に2ステップです。cin.clear() はエラーフラグをリセットしてストリームを再び使えるようにし、cin.ignore(...) はバッファにまだ引っかかっている問題のある文字を捨てます。ignore を省くと不正な入力がそのまま残り、次の >> がまた失敗します — 典型的な無限ループです。cin >> n を条件で直接チェックできるのは、ストリームが失敗状態のとき false に変換されるからです。
避けるべきよくあるミス
- 文に対して
cin >> sを使う。 最初のスペースで止まります。スペースを含むものにはgetlineを使いましょう。 >>とgetlineの間でcin.ignoreを忘れる。 残った改行で空行になります。まずバッファをクリアしましょう。- どこでも
endlに手を伸ばす。 1つ1つがフラッシュを強制します。デフォルトは'\n'にして、endlは出力が本当に今すぐ現れる必要があるときのために取っておきましょう。 - 失敗した
cinを無視する。 数値読み込みでの文字はcinを壊します。再び読む前に必ずclear()してからignore()しましょう。
次へ: 文字列ストリーム
コンソールI/Oと文字列処理は文字列ストリームで1つになります。stringstream は同じ << と >> の演算子を提供しますが、コンソールではなくメモリ上の文字列に向けられています — 1行を数値へ解析したり、整形済みテキストを組み立てたり、キーボードに一切触れずに文字列と他の型を変換したりするのに最適です。
よくある質問
C++でcinを使った直後にgetlineが入力をスキップするのはなぜですか?
cin >> x は数値を読み取りますが、あなたが押した改行をバッファに残します。次の getline はその残った改行までを読み取り、すぐに空文字列を返します。>> の後、getline の前に cin.ignore(numeric_limits<streamsize>::max(), '\n'); でまず取り除いてください。
C++でendlと\nの違いは何ですか?
どちらも行を終わらせますが、endl はさらに出力バッファを画面へフラッシュします。一方 '\n' は改行を挿入するだけです。フラッシュにはコストがあるので、きついループの中では '\n' を選び、ストリームに自分でフラッシュさせましょう。endl は本当に出力を今すぐ表示させたいときだけ使ってください。
C++でスペースを含む1行全体のテキストを読み込むにはどうすればいいですか?
cin >> line ではなく getline(cin, line) を使います。>> 演算子は最初の空白で止まるため、1単語しか取れません。getline はEnterキーまでのすべてを、スペースも含めて std::string に読み込みます。