gpt4 book ai didi

c++ - 为什么std::getline()在格式化提取后会跳过输入?

转载 作者:行者123 更新时间:2023-12-02 10:32:58 34 4
gpt4 key购买 nike

我有以下代码提示用户输入名称和状态:

#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::getline()的默认行为有关。当您提供名称(std::cin >> name)的输入时,您不仅提交了以下字符,而且还向流添加了隐式换行符:

"John\n"

从终端提交时,选择回车或回车时,换行符总是附加到输入中。它也用于文件中以移至下一行。提取到 name之后,换行符将保留在缓冲区中,直到下一个I / O操作被丢弃或消耗为止。当控制流达到 std::getline()时,换行符将被丢弃,但输入将立即停止。发生这种情况的原因是,此功能的默认功能指示它应该这样做(它尝试读取行并在找到换行符时停止)。
因为该换行符禁止了程序的预期功能,所以必须以某种方式跳过或忽略它。一种选择是在第一次提取后调用 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)字符
  • 如果找到分隔符

  • 第三个条件是我们正在处理的条件。您输入的 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准备流,并确定其是否处于故障状态。您只会发现在未格式化的输入函数中,岗亭构造函数的第二个参数是 true。该参数意味着从输入序列的开头不会丢弃前导空格。以下是标准[§27.7.2.1.3/ 2]中的相关报价:
     explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);

    [...] If noskipws is zero and is.flags() & ios_base::skipws is nonzero, the function extracts and discards each character as long as the next available input character c is a whitespace character. [...]


    由于上述条件为假,因此哨兵对象将不会丢弃空白。该功能将 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字符,找到定界符(由第二个参数 delim指定)或到达流的末尾为止。 std::ws仅用于从流的开头丢弃空白字符。
    如果要将格式化的输入与未格式化的输入混合在一起,并且需要丢弃残留的空白,请使用 std::ws。否则,如果您需要清除无效输入而不管它是什么,请使用 ignore()。在我们的示例中,我们仅需要清除空格,因为流消耗了 "John"变量的 name输入。剩下的只是换行符。

    1: std::skipws是操纵器,它告诉输入流在执行格式化输入时放弃前导空白。可以使用 std::noskipws操作器将其关闭。
    2:输入流默认将某些字符视为空格,例如空格字符,换行符,换页符,回车符等。
    3:这是 std::basic_istream<...>::ignore()的签名。您可以使用零个参数来调用它,以从流中丢弃单个字符,用一个参数来丢弃流中的某些字符,或者使用两个参数来丢弃 count字符,或者直到到达 delim为止,以先到者为准。如果您不知道在定界符之前有多少个字符,则通常将 std::numeric_limits<std::streamsize>::max()用作 count的值,但是无论如何都希望将其丢弃。

    关于c++ - 为什么std::getline()在格式化提取后会跳过输入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61602362/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com