为什么std :: getline()在格式化提取之后跳过input?

我有以下代码提示用户input他们的名字和状态:

#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和结果输出:

 Input: "John" "New Hampshire" Output: "Your name is John and you live in " 

为什么输出中省略了州名? 我已经给了正确的input,但代码忽略了它。 为什么会发生?

为什么会发生?

这与你自己提供的input没有多大关系,而是与默认行为std::getline()展品有关。 当你为名称( std::cin >> name )提供了input时,你不仅提交了以下字符,而且在stream中还附加了一个隐含的换行符:

 "John\n" 

当您从terminal提交时select“ Enter”或“ Return”时,换行符总是附加到您的input。 它也被用于移动到下一行的文件中。 在提取到name之前,换行符留在缓冲区中,直到下一个I / O操作被丢弃或消耗。 当控制stream达到std::getline() ,换行符将被丢弃,但input将立即停止。 发生这种情况的原因是因为此函数的默认function指示它应该(它尝试读取一行并在发现新行时停止)。

因为这个领先的换行符会抑制程序的预期function,所以它必须以某种方式被忽略。 一种select是在第一次提取之后调用std::cin.ignore() 。 它将丢弃下一个可用的字符,以便换行不再侵入。


深入的解释:

这是你调用的std::getline()的重载:

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

这个函数的另一个重载是charTtypes的分隔符。 分隔字符是表示input序列之间的边界的字符。 这个特定的重载默认情况下将分隔符设置为换行符input.widen('\n') ,因为没有提供。

现在,这些是std::getline()终止input的一些条件:

  • 如果stream已经提取了std::basic_string<charT>可容纳的最大字符数量
  • 如果find文件结尾(EOF)字符
  • 如果分隔符已被find

第三个条件是我们正在处理的一个。 您的stateinput如下所示:

 "John\nNew Hampshire" ^ | next_pointer 

next_pointer是下一个要parsing的字符。 由于input序列中下一个位置存储的字符是分隔符,因此std::getline()将悄悄丢弃该字符,将next_pointer递增到下一个可用字符并停止input。 这意味着您提供的其余字符仍保留在下一个I / O操作的缓冲区中。 你会注意到,如果你执行另一个从行读取state ,你的提取将产生正确的结果,因为最后一次调用std::getline()放弃了分隔符。


您可能已经注意到,在使用格式化的input运算符( operator>>() )进行提取时,通常不会遇到此问题。 这是因为inputstream使用空格作为分隔符input,并默认情况下设置了std::skipws 1操纵器。 当开始执行格式化input时,stream将从stream中丢弃前导空白。 2

与格式化的input操作符不同, std::getline()是一个无格式的input函数。 所有未格式化的input函数都有以下代码:

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

上面是一个Sentry对象,它在标准的C ++实现中被实例化为所有格式化/未格式化的I / O函数。 Sentry对象用于准备I / Ostream,并确定它是否处于失败状态。 你只会发现在未格式化的input函数中,哨兵构造函数的第二个参数是true 。 这个参数意味着前导空白不会从input序列的开始被丢弃。 以下是标准[§27.7.2.1.3/ 2]中的相关引用:

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

[…]如果noskipws为零且is.flags() & ios_base::skipws不为零,则只要下一个可用的input字符c是空格字符,函数就会提取并丢弃每个字符。 […]

由于上述条件是错误的,哨兵对象不会丢弃空白。 这个函数将noskipws设置为true的原因是因为std::getline()是将未经格式化的原始字符读入std::basic_string<charT>对象。


解决scheme:

没有办法阻止std::getline()这种行为。 你必须做的是在std::getline()运行之前自己放弃新的行(但在格式化的提取之后)。 这可以通过使用ignore()放弃其余的input,直到我们到达一个新的新行:

 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()是一个函数,它放弃指定数量的字符,直到它find一个分隔符或到达stream的末尾( ignore()也会丢弃分隔符,如果它发现它) 。 max()函数返回stream可以接受的最大字符数。

另一种放弃空格的方法是使用std::ws函数,它是一个操纵器,用于从inputstream的开头提取和丢弃前导空格:

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

有什么不同?

不同的是, ignore(std::streamsize count = 1, int_type delim = Traits::eof()) 3不加区分地丢弃字符,直到它丢弃count字符,find分隔符(由第二个参数delim指定)或者结束的stream。 std::ws仅用于从stream的开始处丢弃空白字符。

如果您将格式化的input与未格式化的input混合在一起,并且需要放弃剩余的空格,请使用std::ws 。 否则,如果需要清除无效input,则使用ignore() 。 在我们的例子中,我们只需要清除空白,因为stream使用了namevariables的"John"input。 剩下的就是换行符。


1: std::skipws是操纵器,它告诉inputstream在执行格式化input时放弃前导空格。 这可以closuresstd::noskipws操纵器。

2:inputstream默认将某些字符视为空白,如空格字符,换行符,换页,回车等。

3:这是std::basic_istream<...>::ignore()的签名。 您可以使用零参数来调用它来丢弃stream中的单个字符,放弃一定数量的字符的一个参数或放弃count字符的两个参数,或者直到达到delim (无论哪个先到达)。 如果您不知道分隔符之前有多less个字符,但是您仍然希望放弃它们,则通常使用std::numeric_limits<std::streamsize>::max()作为count的值。

一切都会好的,如果你改变你的初始代码以下列方式:

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