std::getline()がフォーマットされた抽出の後に入力をスキップするのはなぜですか?

c++ input iostream istream c++-faq


次のようなコードを使って、ユーザーに名前と状態の入力を促しています。

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::cin >> name && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
}

私が見つけたのは、名前は正常に抽出されていますが、状態は抽出されていないということです。以下が入力と結果の出力です。

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

なぜ出力された状態の名前が省略されているのでしょうか?適切な入力を与えたのに、コードはなぜか無視してしまいます。なぜこのようなことが起こるのでしょうか?




Answer 1 0x499602D2


なぜこのようなことが起こるのでしょうか?

これは、あなた自身が提供した入力とはほとんど関係ありませんが、デフォルトの動作 std::getline() 示す動作とは関係ありません。名前( std::cin >> name )の入力を提供すると、次の文字を送信しただけでなく、暗黙的な改行もストリームに追加されました。

"John\n"

を選択すると、入力には常に改行が付加されます。EnterorReturn端末から送信する場合。次の行に移動するためのファイルでも使用されます。改行は、 name 抽出された後、破棄または消費される次のI / O操作までバッファに残されます。制御のフローが std::getline() 到達すると、改行は破棄されますが、入力はすぐに停止します。これが発生する理由は、この関数のデフォルトの機能が必要であることを指示しているためです(行を読み取ろうとし、改行が見つかると停止します)。

この先頭の改行はプログラムの期待される機能を阻害するため、無視されたものをスキップする必要があります。1つのオプションは、最初の抽出後に std::cin.ignore() を呼び出すことです。改行が邪魔にならないように、次に利用可能な文字を破棄します。

std::getline(std::cin.ignore(), state)

掘り下げた説明。

これは、呼び出した std::getline() のオーバーロードです。

template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
                                    std::basic_string<charT>& str )

この関数の別のオーバーロードは、 charT 型の区切り文字を取ります。区切り文字は、入力シーケンス間の境界を表す文字です。この特定のオーバーロードは、区切り文字が指定されていないため、区切り文字を改行文字 input.widen('\n') にデフォルトで設定します。

さて、これらは std::getline() 入力を終了させるいくつかの条件です:

  • ストリームが std::basic_string<charT> が保持できる最大文字数を抽出した場合
  • ファイル終端文字(EOF)が見つかった場合
  • 区切り文字が見つかった場合

3番目の条件は、私たちが扱っているものです。 state への入力はこのように表されます:

"John\nNew Hampshire"
     ^
     |
 next_pointer

ここで、 next_pointer は解析される次の文字です。入力シーケンスの次の位置に格納されている文字が区切り文字であるため、 std::getline() は静かにその文字を破棄し、 next_pointer を次の使用可能な文字にインクリメントして、入力を停止します。つまり、指定した残りの文字は、次のI / O操作のためにバッファに残ります。この行から state への別の読み取りを実行すると、 std::getline() への最後の呼び出しで区切り文字が破棄されたため、抽出によって正しい結果が得られることがわかります。


書式付き入力演算子( operator>>() )で抽出する場合、通常この問題に遭遇しないことに気づいたかもしれません。これは、入力ストリームが入力の区切り文字として空白を使用し、デフォルトで std::skipws 1マニピュレーターがオンになっているためです。ストリームは、フォーマットされた入力の実行を開始すると、ストリームから先頭の空白を破棄します。2

フォーマットされた入力演算子とは異なり、 std::getline()フォーマットされていない入力関数です。また、フォーマットされていないすべての入力関数には、次のコードがいくぶん共通しています。

typename std::basic_istream<charT>::sentry ok(istream_object, true);

上記は、標準のC ++実装のすべてのフォーマット済み/未フォーマットI / O関数でインスタンス化される監視オブジェクトです。 Sentryオブジェクトは、ストリームをI / O用に準備し、それが障害状態にあるかどうかを判別するために使用されます。フォーマットされていない入力関数では、sentryコンストラクターの2番目の引数が true であることがわかります。この引数は、入力シーケンスの先頭から先頭の空白が破棄されないことを意味します。標準からの関連引用[§27.7.2.1.3/ 2]:

 explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

[...] noskipws がゼロで、 is.flags() & ios_base::skipws がゼロ以外の場合、関数は、次に使用可能な入力文字 c が空白文字である限り、各文字を抽出して破棄します。[...]

上記の条件が偽であるため、監視オブジェクトは空白を破棄しません。理由 noskipws がに設定されている true この機能によってはポイントからである std::getline() 内生、フォーマットされていない文字を読み取ることである std::basic_string<charT> オブジェクト。


解決策です。

std::getline() この動作を停止する方法はありません。 std::getline() 実行する前に、自分で新しい行を破棄する必要があります(ただし、フォーマットされた抽出の後に行います)。これを行うには、 ignore() を使用して、新しい行に達するまで残りの入力を破棄します。

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

std::numeric_limits を使用するには、 <limits> を含める必要があります。 std::basic_istream<...>::ignore() は、区切り文字が見つかるか、ストリームの最後に到達するまで、指定された文字数を破棄する関数です( ignore() も、区切り文字が見つかれば破棄します) 。 max() 関数は、ストリームが受け入れることができる文字の最大量を返します。

空白を破棄する別の方法は、入力ストリームの先頭から先頭の空白を抽出して破棄するように設計されたマニピュレーターである std::ws 関数を使用することです。

if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }

違いは何ですか?

違いは、 ignore(std::streamsize count = 1, int_type delim = Traits::eof()) 3は、 count 文字を破棄するか、区切り文字(2番目の引数 delim で指定)を見つけるか、末尾に到達するまで、無差別に文字を破棄することです。ストリームの。 std::ws は、ストリームの先頭から空白文字を破棄するためにのみ使用されます。

フォーマットされた入力とフォーマットされていない入力を混在させており、残りの空白を破棄する必要がある場合は、 std::ws 使用します。それ以外の場合に、無効な入力をクリアする必要がある場合は、 ignore() を使用してください。この例では、ストリームが name 変数の "John" の入力を消費したため、空白のみをクリアする必要があります。残ったのは改行文字だけでした。


1: std::skipws は、フォーマットされた入力を実行するときに先頭の空白を破棄するように入力ストリームに指示するマニピュレーターです。これは std::noskipws マニピュレーターでオフにすることができます。

2:入力ストリームは、デフォルトで、空白文字、改行文字、フォームフィード、キャリッジリターンなどの特定の文字を空白と見なします。

3:これは std::basic_istream<...>::ignore() のシグネチャです。引数を0として呼び出して、ストリームから1文字を破棄するか、1つの引数で特定の count 文字を破棄するか、2つの引数でcount文字を破棄するか、または delim に到達するまでのいずれか早い方で呼び出します。区切り文字の前にある文字数がわからないが、とにかく破棄したい場合は、通常、 count の値として std::numeric_limits<std::streamsize>::max() を使用します。




Answer 2 Boris


以下の方法で初期コードを変更すれば全てOKです。

if ((cin >> name).get() && std::getline(cin, state))



Answer 3 Justin Randall


これは、改行を開始するようにストリームに指示するときに、端末からのすべてのユーザー入力に改行文字 \n とも呼ばれる暗黙的な改行が追加されるために発生します。ユーザー入力の複数行をチェックするときに std::getline を使用することで、これを安全に説明できます。 std::getline のデフォルトの動作は、この場合 std::cin である入力ストリームオブジェクトから改行文字 \n までのすべてを読み取ります。

#include <iostream>
#include <string>

int main()
{
    std::string name;
    std::string state;

    if (std::getline(std::cin, name) && std::getline(std::cin, state))
    {
        std::cout << "Your name is " << name << " and you live in " << state;
    }
    return 0;
}
Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in New Hampshire"