c++ - 为什么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 "

为什么在输出中省略了状态名称?我已经给出了正确的输入,但是代码以某种方式忽略了它。为什么会这样?

最佳答案

为什么会这样?
这与您自己提供的输入无关,而与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/64487210/

    相关文章:

    c++ - "constify" `std::pair` 的字段是否可以不被黑客攻击?

    c++ - 在 C++ 中将结构内的数组归零

    Python 3.x : User Input to Call and Execute a Function

    javascript - 将数据从 URL 注入(inject)到输入框

    c++ - 无法从文本文件中读取并存储到字符串中

    c++ - 在没有 vector ,指针的情况下在c++中在运行时增加数组大小

    c++ - Linux futex 5.4 操作系统内核版本 2.6.9 出现问题

    C++ popen 没有得到所有的标准输出

    css - IE8 - 文本区域焦点上的站点背景更改

    C++控制台屏幕大小