为什么会这样?
这与您自己提供的输入无关,而与默认行为std::getline()
显示有关。当您输入名称(std::cin >> name
)时,您不仅提交了以下字符,而且还向流添加了隐式换行符:
"John\n"
选择Enter或Return从终端提交时,换行符总是附加到输入中。它也用于文件中以移至下一行。提取后,换行符将保留在缓冲区中,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 )
此函数的另一个重载采用type的定界符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);
[...]如果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))
{ ... }
您需要<limits>
使用std::numeric_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
字符,找到定界符(由第二个参数指定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
,但是无论如何都希望将其丢弃。
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
也应该能按预期工作。(除了下面的答案)。