下面的代码提示用户输入他们的名称和状态:
#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"
从终端提交时,当您选择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 )
此函数的另一个重载采用chart
类型的分隔符。 分隔符字符是表示输入序列之间边界的字符。 默认情况下,此特定重载将分隔符设置为换行字符input.widen('\n')
,因为没有提供一个。
下面是std::getLine()
终止输入的几个条件:
std::basic_string
可以容纳的最大字符量第三个条件是我们要处理的。 您对state
的输入如下所示:
"John\nNew Hampshire"
^
|
next_pointer
其中next_pointer
是要解析的下一个字符。 由于存储在输入序列中下一个位置的字符是分隔符,std::getLine()
将悄悄地丢弃该字符,将next_pointer
递增到下一个可用字符,并停止输入。 这意味着您提供的其余字符仍然保留在缓冲区中,以供下一个I/O操作使用。 您将注意到,如果再次从行中读取state
,则提取将产生正确的结果,因为对std::getLine()
的最后一次调用丢弃了分隔符。
您可能已经注意到,在使用格式化输入运算符(operator>>>()
)进行解压时通常不会遇到此问题。 这是因为输入流使用空白作为输入的分隔符,并且默认情况下将std::skipws
1操作器设置为on。 流将在开始执行格式化输入时丢弃流中的前导空格。2
与格式化输入运算符不同,std::getLine()
是一个未格式化的输入函数。 所有未格式化的输入函数都有以下相同的代码:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
上面是一个sentry对象,它在标准C++实现中的所有格式化/非格式化I/O函数中被实例化。 哨兵对象用于为I/O准备流,并确定流是否处于失败状态。 您只会发现在未格式化的输入函数中,sentry构造函数的第二个参数是true
。 该参数意味着不会从输入序列的开始丢弃前导空格。 以下是标准[§27.7.2.1.3/2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] 如果noskipws
为零且为.flags()& IOS_BASE::skipws
为非零,则只要下一个可用输入字符C
是空白字符,函数就提取并丢弃每个字符。 [...]
由于上述条件为false,哨兵对象不会丢弃空格。 此函数之所以将noskipws
设置为true
,是因为std::getLine()
的作用是将未格式化的原始字符读入std::basic_string
对象。
没有办法停止std::getLine()
的这种行为。 您必须做的是在std::getLine()
运行之前自己丢弃新行(但在格式化提取之后执行)。 这可以通过使用ignore()
丢弃输入的其余部分来实现,直到我们到达一个新的新行:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
您需要包含
才能使用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()
。 在我们的示例中,我们只需要清除空格,因为流消耗了name
变量的“john”
输入。 只剩下换行符了。
1:std::skipws
是操纵器,它告诉输入流在执行格式化输入时丢弃前导空格。 这可以用std::noskipws
操作器关闭。
2:输入流默认情况下将某些字符视为空白,例如空格字符,换行符,表单提要,回车符等。
3:这是std::basic_istream<...>::ignore()
的签名。 您可以用零个参数调用它以从流中丢弃单个字符,用一个参数来丢弃一定量的字符,或者用两个参数来丢弃count
字符或直到它到达delim
,以先到的一个为准。 如果您不知道分隔符前有多少个字符,但又想要丢弃它们,则通常使用std::numeric_limits
作为count
的值。
如果您按以下方式更改初始代码,一切都将正常:
if ((cin >> name).get() && std::getline(cin, state))
发生这种情况的原因是一个隐式换行符(也称为换行符\n
)被附加到来自终端的所有用户输入中,因为它告诉流开始一个新行。 您可以在检查多行用户输入时使用std::getLine
来安全地说明这一点。 std::getLine
的默认行为将从输入流对象(在本例中为std::cin
)中读取直到(包括)换行符\n
为止的所有内容。
#include <iostream>
#include <string>
int main()
{
std::string name;
std::string state;
if (std::getline(std::cin, name) && std::getline(std::cin, state))
{
std::cout << "Your name is " << name << " and you live in " << state;
}
return 0;
}
Input:
"John"
"New Hampshire"
Output:
"Your name is John and you live in New Hampshire"