为什么std :: getline()在格式化提取后跳过输入? [英] Why does std::getline() skip input after a formatted extraction?

查看:294
本文介绍了为什么std :: getline()在格式化提取后跳过输入?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  #include< iostream> 
#include< string>

int main()
{
std :: string name;
std :: string state;

if(std :: cin>> name&& std :: getline(std :: cin,state))
{
std :: cout ;& 您的姓名是<名称<< 你住在<州;
}
}

我发现的名称已经成功提取,但不是国家。这里是输入和结果输出:


 输入:

John
New Hampshire

输出:

你的名字是John,你住在
/ pre>

为什么输出中省略了状态名称?我给了适当的输入,但代码不知何故忽略它。为什么会发生这种情况?

解决方案

这与您提供的输入无关,而是与默认行为 std :: getline()展示无关。当您为名称( std :: cin>> name )提供输入时,您不仅提交了以下字符,而且还将隐式换行符附加到流:


 John \\\

p>

当您选择输入时, 从终端提交时。它也用于文件中向下移动。在提取到 name 之后,换行符留在缓冲区中,直到下一个I / O操作被丢弃或消耗。当控制流达到 std :: getline()时,换行符将被丢弃,但输入将立即停止。发生这种情况的原因是这个函数的默认功能指示它应该(它尝试读取一行并在找到一个换行符时停止。)



换行符抑制了你的程序的预期功能,因此它必须跳过我们忽略的某种方式。一个选项是在第一次提取后调用 std :: cin.ignore()






深入说明:



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


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


此函数的另一个重载使用 charT 类型的定界符。分隔符字符是表示输入序列之间的边界的字符。这个特殊的重载默认设置分隔符为换行符 input.widen('\\\
')
,因为没有提供。



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




  • 如果流已提取了最大字符数 可以保存 std :: basic_string< charT> b
  • 如果找到文件结束符(EOF)字符

  • 如果找到分隔符



第三个条件是我们要处理的。您输入 state 的结果如下:


 John \\\
New 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); 

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


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

[...]如果 noskipws 0和 is.flags()& ios_base :: skipws 非零,只要下一个可用输入字符 c 是空格字符,该函数就会提取并丢弃每个字符。 [...]


由于上述条件为false,sentry对象不会丢弃空格。此函数将 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& (),'\\\
')&&
std :: getline(std :: cin,state))
{...}

您需要包含< limits> 才能使用 std :: numeric_limits std :: basic_istream< ...> :: ignore()是一个函数,它丢弃指定数量的字符,直到找到分隔符或到达stream( 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 个字符,找到定界符(由第二个参数 )或命中流的结尾。 std :: ws 仅用于从流头开始丢弃空格字符。



带有未格式化输入的格式化输入,并且您需要丢弃剩余空白,使用 std :: ws 。否则,如果你需要清除无效输入,不管它是什么,使用 ignore()。在我们的示例中,我们只需要清除空格,因为流John name 变量。剩下的就是换行符。






1: std :: skipws 是操作器,它告诉输入流在执行格式化输入时丢弃前导空白。这可以用 std :: noskipws 操纵器关闭。



默认情况下,输入流将某些字符视为空格,例如空格字符,换行符,换行符,回车符等。



这是 std :: basic_istream< ...> :: ignore()的签名。您可以使用零参数调用它从流中丢弃单个字符,一个参数丢弃一定量的字符,或两个参数丢弃 count 个字符或直到它达到 delim ,以先到者为准。通常使用 std :: numeric_limits< std :: streamsize> :: max()作为 count 的值if你不知道在分隔符之前有多少个字符,但你仍然要丢弃它们。


I have the following piece of code that prompts the user for their name and state:

#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;
    }
}

What I find is that the name has been successfully extracted, but not the state. Here is the input and resulting output:

Input:

"John"
"New Hampshire"

Output:

"Your name is John and you live in "

Why has the name of the state been omitted from the output? I've given the proper input, but the code somehow ignores it. Why does this happen?

解决方案

Why does this happen?

This has little to do with the input you provided yourself but rather with the default behavior std::getline() exhibits. When you provided your input for the name (std::cin >> name), you not only submitted the following characters, but also an implicit newline was appended to the stream:

"John\n"

A newline is always appended to your input when you select Enter or Return when submitting from a terminal. It is also used in files for moving toward the next line. The newline is left in the buffer after the extraction into name until the next I/O operation where it is either discarded or consumed. When the flow of control reaches std::getline(), the newline will be discarded, but the input will cease immediately. The reason this happens is because the default functionality of this function dictates that it should (it attempts to read a line and stops when it finds a newline).

Because this leading newline inhibits the expected functionality of your program, it follows that it must be skipped our ignored somehow. One option is to call std::cin.ignore() after the the first extraction. It will discard the next available character so that the newline is no longer intrusive.


In-Depth Explanation:

This is the overload of std::getline() that you called:

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

Another overload of this function takes a delimiter of type charT. A delimiter character is a character that represents the boundary between sequences of input. This particular overload sets the delimiter to the newline character input.widen('\n') by default since one was not supplied.

Now, these are a few of the conditions whereby std::getline() terminates input:

  • If the stream has extracted the maximum amount of characters a std::basic_string<charT> can hold
  • If the end-of-file (EOF) character has been found
  • If the delimiter has been found

The third condition is the one we're dealing with. Your input into state is represented thusly:

"John\nNew Hampshire"
     ^
     |
 next_pointer

where next_pointer is the next character to be parsed. Since the character stored at the next position in the input sequence is the delimiter, std::getline() will quietly discard that character, increment next_pointer to the next available character, and stop input. This means that the rest of the characters that you have provided still remain in the buffer for the next I/O operation. You'll notice that if you perform another read from the line into state, your extraction will yield the correct result as the last call to std::getline() discarded the delimiter.


You may have noticed that you don't typically run into this problem when extracting with the formatted input operator (operator>>()). This is because input streams use whitespace as delimiters for input and have the std::skipws1 manipulator set on by default. Streams will discard the leading whitespace from the stream when beginning to perform formatted input.2

Unlike the formatted input operators, std::getline() is an unformatted input function. And all unformatted input functions have the following code somewhat in common:

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

The above is a sentry object which is instantiated in all formatted/unformatted I/O functions in a standard C++ implementation. Sentry objects are used for preparing the stream for I/O and determining whether or not it is in a fail state. You'll only find that in the unformatted input functions, the second argument to the sentry constructor is true. That argument means that leading whitespace will not be discarded from the beginning of the input sequence. Here is the relevant quote from the Standard [§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. [...]

Since the above condition is false, the sentry object will not discard the whitespace. The reason noskipws is set to true by this function is because the point of std::getline() is to read raw, unformatted characters into a std::basic_string<charT> object.


The Solution:

There's no way to stop this behavior of std::getline(). What you'll have to do is discard the new line yourself before std::getline() runs (but do it after the formatted extraction). This can be done by using ignore() to discard the rest of the input until we reach a fresh new line:

if (std::cin >> name &&
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
    std::getline(std::cin, state))
{ ... }

You'll need to include <limits> to use std::numeric_limits. std::basic_istream<...>::ignore() is a function that discards a specified amount of characters until it either finds a delimiter or reaches the end of the stream (ignore() also discards the delimiter if it finds it). The max() function returns the largest amount of characters that a stream can accept.

Another way to discard the whitespace is to use the std::ws function which is a manipulator designed to extract and discard leading whitespace from the beginning of an input stream:

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

What's the difference?

The difference is that ignore(std::streamsize count = 1, int_type delim = Traits::eof())3 indiscriminately discards characters until it either discards count characters, finds the delimiter (specified by the second argument delim) or hits the end of the stream. std::ws is only used for discarding whitespace characters from the beginning of the stream.

If you are mixing formatted input with unformatted input and you need to discard residual whitespace, use std::ws. Otherwise, if you need to clear out invalid input regardless of what it is, use ignore(). In our example, we only need to clear whitespace since the stream consumed your input of "John" for the name variable. All that was left was the newline character.


1: std::skipws is manipulator that tells the input stream to discard leading whitespace when performing formatted input. This can be turned off with the std::noskipws manipulator.

2: Input streams deem certain characters as whitespace by default, such the space character, newline character, form feed, carriage return, etc.

3: This is the signature of std::basic_istream<...>::ignore(). You can call it with zero arguments to discard a single character from the stream, one argument to discard a certain amount of characters, or two arguments to discard count characters or until it reaches delim, whichever one comes first. You normally use std::numeric_limits<std::streamsize>::max() as the value of count if you don't know how many characters there are before the delimiter, but you want to discard them anyway.

这篇关于为什么std :: getline()在格式化提取后跳过输入?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆