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


105

ユーザーに名前と状態の入力を求める次のコードがあります。

#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 "

状態の名前が出力から省略されたのはなぜですか?適切な入力を指定しましたが、コードはどういうわけかそれを無視します。なぜこれが起こるのですか?


私は信じてstd::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)期待通りにも動作するはずです。(以下の回答に加えて)。
JWW

回答:


122

なぜこれが起こるのですか?

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

"John\n"

端末から選択EnterまたはReturn送信するときは、常に改行が入力に追加されます。次の行に移動するためのファイルでも使用されます。改行は、抽出後、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::skipws1つのマニピュレータがオンになっているためです。ストリームは、フォーマットされた入力の実行を開始すると、ストリームから先頭の空白を破棄します。2

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

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

上記は、標準C ++実装のすべてのフォーマット済み/未フォーマットI / O関数でインスタンス化される監視オブジェクトです。Sentryオブジェクトは、I / Oのためにストリームを準備し、それが障害状態にあるかどうかを判断するために使用されます。フォーマットされていない入力関数では、セントリーコンストラクターの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が空白文字である限り、各文字を抽出して破棄します。[...]

上記の条件はfalseであるため、監視オブジェクトは空白を破棄しません。この関数によって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))
{ ... }

<limits>を使用するには、を含める必要がありますstd::numeric_limitsstd::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()。この例では、ストリーム"John"name変数の入力を消費したため、空白をクリアするだけで済みます。残ったのは改行文字だけでした。


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

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

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


1
なぜ単純ではないのですif (getline(std::cin, name) && getline(std::cin, state))か?
Fred Larson

@FredLarson良い点。ただし、最初の抽出が整数または文字列ではないものである場合は機能しません。
0x499602D2

もちろん、ここではそうではなく、同じことを2つの異なる方法で行う意味はありません。整数の場合、行を文字列に変換してを使用できますがstd::stoi()、利点があるかどうかは明確ではありません。しかし、私は単にstd::getline()行指向の入力に使用し、意味のある方法で行を解析することを好む傾向があります。エラーが発生しにくいと思います。
Fred Larson

@FredLarson同意する。時間があれば、追加するかもしれません。
0x499602D2

1
@Albin使用する理由std::getline()は、特定の区切り文字までのすべての文字をキャプチャして文字列に入力する場合です。デフォルトでは、改行です。これらXの文字列の数が単一の単語/トークンである場合、この作業はで簡単に実行できます>>。それ以外の場合は、最初の数値をで整数に入力し、次の行で>>呼び出してcin.ignore()から、を使用するループを実行しますgetline()
0x499602D2

11

次の方法で初期コードを変更すると、すべてが正常になります。

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

3
ありがとうございました。get()次の文字を消費するため、これも機能します。(std::cin >> name).ignore()私の回答の前半で提案したものもあります。
0x499602D2

".. get()のために動作する..."はい、その通りです。詳細なしで回答してくれて申し訳ありません。
ボリス

4
なぜ単純ではないのですif (getline(std::cin, name) && getline(std::cin, state))か?
Fred Larson

0

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

#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"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.