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

我有以下一段代码,提示用户输入他们的猫的年龄和名字:

#include <iostream>
#include <string>


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


std::cin >> age;
std::getline(std::cin, name);
    

if (std::cin)
{
std::cout << "My cat is " << age << " years old and their name is " << name << std::endl;
}
}

我发现的是,年龄已经被成功地解读了,但名字却没有。下面是输入和输出:

Input:


"10"
"Mr. Whiskers"


Output:


"My cat is 10 years old and their name is "

为什么从输出中省略了名称?我给出了正确的输入,但代码却忽略了它。为什么会这样?

30458 次浏览

为什么会这样?

这与您自己提供的输入几乎没有关系,而是与 std::getline()的默认行为有关。当您提供年龄(std::cin >> age)的输入时,您不仅提交了以下字符,而且在输入 Enter时在流中添加了一个隐式换行符:

"10\n"

当您从终端提交时选择 EnterReturn时,输入总是附加一个换行符。它还用于文件中,用于移动到下一行。在提取到 age之后,换行符将保留在缓冲区中,直到下一个 I/O 操作丢弃或读取它。当控制流到达 std::getline()时,它将看到 "\nMr. Whiskers"并且开始的换行将被丢弃,但是输入操作将立即停止。发生这种情况的原因是,std::getline()的工作是尝试读取字符并在找到换行符时停止。因此,输入的其余部分留在缓冲区中未读。

解决方案

cin.ignore()

要解决这个问题,一个选项是在执行 std::getline()之前跳过换行。可以通过在第一个输入操作之后调用 std::cin.ignore()来实现这一点。它将丢弃下一个字符(换行字符) ,以便它不再阻碍。

std::cin >> age;
std::cin.ignore();
std::getline(std::cin, name);


assert(std::cin);
// Success!

std::ws

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

std::cin >> age;
std::getline(std::cin >> std::ws, name);


assert(std::cin);
// Success!

std::cin >> std::ws表达式在 std::getline()调用之前(以及在 std::cin >> age调用之后)执行,以便删除换行符。

不同之处在于,ignore()只丢弃1个字符(给定参数时为 N 个字符) ,而 std::ws继续忽略空格,直到找到一个非空格字符。因此,如果您不知道下一个标记之前会有多少空格,那么您应该考虑使用这种方法。

匹配操作

当您遇到这样的问题时,通常是因为您将格式化输入操作与非格式化输入操作相结合。格式化输入操作是指将输入设置为特定类型并对其进行格式化。这就是 operator>>()的作用。未格式化的输入操作可以是其他任何操作,比如 std::getline()std::cin.read()std::cin.get()等等。这些函数不关心输入的格式,只处理原始文本。

如果你坚持使用单一类型的格式,那么你可以避免这个恼人的问题:

// Unformatted I/O
std::string age, name;
std::getline(std::cin, age);
std::getline(std::cin, name);

或者

// Formatted I/O
int age;
std::string firstName, lastName;
std::cin >> age >> firstName >> lastName;

如果您选择使用未格式化的操作将所有内容读取为字符串,那么您可以在以后将它们转换为适当的类型。

如果你以下面的方式修改你的初始代码,一切都会好起来的:

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"

由于上面的每个人都回答了输入 10\nMr Whisker\n的问题,我想回答一个不同的方法。上述所有解决方案都发布了如果缓冲区类似于 10\nMr Whisker\n的代码。但是,如果我们不知道用户在提供输入时的行为。用户可能错误地键入 10\n\nMr. Whisker\n10 \n\n Mr. whisker\n。在这种情况下,上面的代码可能不起作用。因此,我使用下面的函数获取字符串输入来解决这个问题。

string StringInput()  //returns null-terminated string
{
string input;
getline(cin, input);
while(input.length()==0)//keep taking input as long as valid string is taken
{
getline(cin, input);
}
return input.c_str();
}

所以,答案是:

#include <iostream>
#include <string>


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


std::cin >> age;
name = StringInput();
    

std::cout << "My cat is " << age << " years old and it's name is " << name << std::endl;
    

}

附加说明:

如果用户输入 a \n10\n \nmr. whiskey; 为了检查 int输入是否有效,这个函数可以用来检查 int输入(如果输入的是 char而不是 int,程序会有未定义行为) :


//instead of "std::cin>>age;" use "get_untill_int(&age);" in main function.
void get_Untill_Int(int* pInput)//keep taking input until input is `int or float`
{
cin>> *pInput;
/*-----------check input validity----------------*/
while (!cin)
{
cin.clear();
cin.ignore(100, '\n');
cout<<"Invalid Input Type.\nEnter again: ";
cin >>*pInput;
}
/*-----------checked input validity-------------*/
}

我真的很好奇。C + + 有一个专门的函数来消耗剩余的或者其他的空白。它叫做 性病。然后,你可以简单地使用

std::getline(std::cin >> std::ws, name);

对于应该使用的格式化输入到非格式化输入之间的每次转换,应该使用惯用方法。

如果我们不是在讨论空格,而是在需要数字的地方输入例如字母,那么我们应该遵循 CPP 引用并使用

.ignore(std::numeric_limits<std::streamsize>::max(), '\n');去除错误的东西。

请读 给你