プログラミング

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の入門へ進めます。

← 記事一覧へ戻る