C++でcinの後のgetlineが入力を飛ばす原因と直し方
cinで数値を読んだ直後にgetlineが空文字列になる理由を解説。ignoreとwsの違い、空行を残す入力例、練習問題と解答を紹介します。
年齢を入れたあと、名前を聞かれるはずが、そのままプログラムが終わってしまう。std::cin >> ageの直後にstd::getlineを使うと、こうした動きになることがあります。
原因として確認したいのは、数字の後ろに残った改行です。今回はC++17以降で、「年齢と名前を別々の行で入力する」プログラムを直します。入力の基本は変数・型・入力の回から確認できます。
getlineが空になるのはなぜ?
まず、問題が起きる例です。
#include <iostream>
#include <string>
int main() {
int age = 0;
std::string name;
std::cout << "年齢: ";
if (!(std::cin >> age)) {
return 1;
}
std::cout << "名前: ";
std::getline(std::cin, name);
std::cout << "name=[" << name << "]\n";
}
18を入力してEnterを押すと、名前を入力する前にname=[]と表示されます。
文字を順番に受け取る仕組みを入力ストリームと呼びます。この例で入力した内容は、18と改行です。整数を読む>>は18までを読み取り、後ろの改行を残します。
入力の並び 1 8 \n
数値入力が読む 1 8
残っているもの \n
\nは改行の表記です。次のgetlineは改行までを1行として読むので、最初に改行が来ると空の文字列になります。改行自体は取り除かれますが、文字列の中へ入りません。数値入力の仕様、getlineの仕様
この場合は、空の1行を正しく読んだ結果です。入力に成功したかの確認だけでは、意図どおりの名前は受け取れません。
改行まで捨ててから次の行を読む
数値入力とgetlineの間に、今の行の残りを読み捨てる処理を入れます。
#include <iostream>
#include <limits>
#include <string>
int main() {
int age = 0;
std::string name;
std::cout << "年齢: ";
if (!(std::cin >> age)) {
std::cout << "整数を入力してください。\n";
return 1;
}
std::cin.ignore(
std::numeric_limits<std::streamsize>::max(), '\n'
);
std::cout << "名前: ";
if (!std::getline(std::cin, name)) {
return 1;
}
std::cout << name << "さんは" << age << "歳です。\n";
}
年齢へ18、その次の行へAoi Soraを入力すると、最後にAoi Soraさんは18歳です。と表示します。空白を含む名前も1行として受け取れます。
ignoreは文字を読み捨てる操作です。長いstd::numeric_limits<std::streamsize>::max()は、この操作では文字数による打ち切りをしない特別な指定です。改行か入力の終わりまで進み、改行も取り除きます。<limits>は、この指定のために読み込んでいます。ignoreの仕様
大事なのは置く場所です。getlineの後では、すでに空行を読んでいます。数値を読めたことを確認したあと、次の行を読む前へ置きましょう。
ignoreとclearは役割が違う
ignoreだけを短くすると1文字しか捨てない
std::cin.ignore();
引数を省略すると、既定では1文字を捨てます。残りが改行だけなら動きますが、数字の後ろにスペースがあると、そのスペースだけを捨てて改行が残る場合があります。
今回の目的は「年齢の行を終えて次の行へ進む」なので、改行まで捨てる指定を使います。ただし、18 Aoi Soraのように同じ行へ書くと、名前も捨てます。別々の行で入力するという前提は必要です。
clearは残った文字を消す処理ではない
std::cin.clear()は、読み取り失敗などを表す状態を変更する操作です。改行や入力文字を消す操作ではありません。ストリームの状態操作
整数欄にabcを入れたあと再入力させるなら、失敗状態を戻す処理と、不要な入力を捨てる処理の両方を考えます。上の完成例は、整数として読めなかった時点で終了する設計です。
また、18abcは先頭の18を数値として読める場合があります。ignoreは残りのabcを捨てるだけで、不正な年齢入力を検査しているわけではありません。行全体を厳密に調べる方法は点数管理プログラムで扱っています。
std::wsを使える場合と使わない方がよい場合
名前など、先頭の空白が不要なら、次の書き方もあります。
std::getline(std::cin >> std::ws, name);
std::wsは先頭のスペース、タブ、改行などを読み飛ばす指定です。短く書けますが、Enterだけの空行も飛ばします。wsの仕様
例えば「コメントは空欄でもよい」「文章の先頭の空白を残したい」という用途では、その情報を消してしまいます。1行目の残りだけを捨てて次の行をそのまま受け取るなら、ignoreと通常のgetlineを使います。短い方を無条件で採用せず、何を入力として残したいかで選びましょう。
練習:空欄でもよいコメントを受け取る
点数を整数で読み、次の行からコメントを受け取ります。コメントは空行も受け付ける条件で、入力を飛ばさないプログラムを書いてください。
模範解答
#include <iostream>
#include <limits>
#include <string>
int main() {
int score = 0;
std::string comment;
std::cout << "点数: ";
if (!(std::cin >> score)) {
std::cout << "整数を入力してください。\n";
return 1;
}
std::cin.ignore(
std::numeric_limits<std::streamsize>::max(), '\n'
);
std::cout << "コメント: ";
if (!std::getline(std::cin, comment)) {
return 1;
}
std::cout << score << "点\n";
std::cout << "コメント=[" << comment << "]\n";
}
解説
最初のignoreで捨てるのは点数の行の残りです。その次の行はgetlineが読むため、Enterだけなら空のコメントとして受け取れます。
80と入力したあと、次の行でよくできたと入力すればコメント=[よくできた]です。コメントを空行にすればコメント=[]になります。空欄を認める条件なので、ここではstd::wsを使いません。
文字列全体の操作を覚えたい場合は、配列・vector・stringの入門へ進めます。