为什么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 )
这个函数的另一个重载是charT
types的分隔符。 分隔字符是表示input序列之间的边界的字符。 这个特定的重载默认情况下将分隔符设置为换行符input.widen('\n')
,因为没有提供。
现在,这些是std::getline()
终止input的一些条件:
- 如果stream已经提取了
std::basic_string<charT>
可容纳的最大字符数量 - 如果find文件结尾(EOF)字符
- 如果分隔符已被find
第三个条件是我们正在处理的一个。 您的state
input如下所示:
"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_limits
。 std::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使用了name
variables的"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))