为什么在读取输入后要调用cin.clear()和cin.ignore()?

121

Google Code University的C++教程以前有这段代码:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

cin.clear()cin.ignore()有什么意义?为什么需要10000\n这些参数?


17
这是我有史以来最被点赞的帖子,远远超过其他所有帖子。我想我在高中时达到了巅峰。 - JacKeown
4个回答

135
cin.clear()清除cin上的错误标志(以便将来的I/O操作能够正确工作),然后cin.ignore(10000,'\n')跳到下一个换行符(忽略与非数字相同行中的任何其他内容,以免导致另一个解析失败)。它只会跳过最多10000个字符,因此代码假定用户不会输入非常长的无效行。

72
+1. 我想补充一点,与其忽略多达10000个字符,最好使用cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); - Dennis
36
如果您想使用 std::numeric_limits,请确保 #include <limits> - gbmhunter
3
可以有人解释一下 std::numeric_limits<std::streamsize>::max() 的语法吗? - Minh Tran
4
@Minh Tran:std::streamsize 是有符号整数型,表示 I/O 传输的字符数或 I/O 缓冲区的大小。在这里,通过使用 "numeric_limits" 模板类,我们想要知道 I/O 缓冲区或传输字符的最大限制。 - Pankaj
@Pankaj 谢谢 - 这有助于我理解调用的功能。如果我理解正确,numeric_limits 是一个模板类,在其中以你所描述的方式为 streamsize 类定义了 max() - Minh Tran
2
@Minh Tran:只有一个小修正,“streamsize”不是一个类,它只是一个整数类型。我们也可以获得其他的限制,比如int、char等,请在[链接](http://en.cppreference.com/w/cpp/types/numeric_limits)上查看。 - Pankaj

57
你输入了
if (!(cin >> input_var))

在从cin读取输入时,如果发生错误,会产生一个错误标志,并且以后的尝试获取输入将会失败。这就是为什么你需要

cin.clear();

为了消除错误标志。而且,我假设失败的输入会停留在某种缓冲区中。当您再次尝试获取输入时,它将读取缓冲区中相同的输入,并再次失败。这就是为什么您需要

cin.ignore(10000,'\n');

它从缓冲区中取出10000个字符,但如果遇到换行符(\n)就停止。这里的10000只是一个通用的大值。


11
补充一下,忽略字符时的默认设置是跳过一个字符,因此你需要更大的数字才能跳过整行。 - Bo Persson

46
为什么我们要使用:
1)cin.ignore 2)cin.clear

?

简单来说:

1)忽略(提取并丢弃)我们不想要的流中的值

2)清除流的内部状态。使用cin.clear后,内部状态再次设置为goodbit,这意味着没有“错误”。

详细说明:

如果将某些内容放在“流”(cin)上,则必须从中取出。通过“取出”,我们指的是从流中“使用”,“删除”,“提取”。流具有流动性。数据像水一样在cin上流动。您无法停止水的流动;)

看下面的例子:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

如果用户在第一个问题中回答:“Arkadiusz Wlodarczyk”会发生什么?

运行程序以自行查看。

您将在控制台上看到“Arkadiusz”,但程序不会要求您输入“年龄”。 它将在打印“Arkadiusz”后立即完成。

而“Wlodarczyk”没有显示。 看起来好像它已经消失了(?)*

发生了什么?;-)

因为“Arkadiusz”和“Wlodarczyk”之间有一个空格。

姓名和姓氏之间的“空格”字符是计算机的一个标志,表示有两个变量正在等待从“输入”流中提取。

计算机认为您正在尝试发送多个变量到输入。 这个“空格”符号是他解释这种方式的标志。

计算机将“Arkadiusz”分配给'name'(2),因为您在流(输入)上放置了多个字符串,计算机将尝试将值“Wlodarczyk”分配给变量'age'(!)。用户没有机会在第6行的'cin'上放置任何内容,因为该指令已经执行(!)。为什么?因为流中仍然有一些内容。正如我之前所说,流是连续不断的,因此必须尽快从中删除所有内容。当计算机看到指令cin >> age;时,这种可能性就出现了。
计算机不知道您创建了一个存储某人年龄的变量(第4行)。对于计算机来说,“age”只是一个标签。对于计算机来说,“age”也可以称为:“afsfasgfsagasggas”,它是相同的。对于他来说,它只是一个变量,他将尝试将“Wlodarczyk”分配给它,因为您在第6行中命令/指示计算机这样做。
这样做是错误的,但是嘿,是您这样做的!这是你的错!好吧,也许是用户,但仍然...

好的好的。但是怎么修复呢?!

在我们正确修复之前,让我们试着玩弄一下这个例子,学习一些更有趣的东西 :-)

我更喜欢采用理解事物的方法。没有知识的情况下修复问题并不能带来满足感,你不觉得吗? :)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

调用上述代码后,您会注意到流(cin)的状态等于4(第7行)。这意味着它的内部状态不再等于goodbit。出了点问题。很明显,你试图将字符串类型值(“Wlodarczyk”)分配给int类型变量'age'。类型不匹配。现在是通知有问题的时候了。计算机通过更改流的内部状态来表示这一点。就像:“你搞砸了,修复我吧。我“友好地”通知你;-)”
您不能再使用'cin'(流)。它被卡住了。就像你把大木头放在水流上一样。您必须在使用它之前修复它。因为木头的障碍(内部状态),数据(水)无法从该流(cin)中获取。
哦,所以如果有障碍物(木材),我们可以使用专门用于此目的的工具将其移除?
是的!
cin的内部状态设置为4就像正在发出警报并发出噪音。

cin.clear可以将状态清除回到正常状态(goodbit)。就像你来了并且关闭了警报一样。你只是把它关掉了。你知道发生了什么事情,所以你说:“停止发出噪音,我已经知道出了什么问题,闭嘴(clear)”。

好的,让我们这样做!让我们使用cin.clear()。

使用“Arkadiusz Wlodarczyk”作为第一个输入来调用下面的代码:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

我们可以在执行上述代码后看到状态等于goodbit。
很好,问题解决了吗?
使用“Arkadiusz Wlodarczyk”作为第一个输入调用下面的代码:
string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

尽管在第9行之后状态被设置为goodbit,但用户没有被要求输入“年龄”。程序停止了。

为什么?!

哦,天啊...你刚刚关闭了警报,那么水里的木头日志怎么办呢?*回到我们谈论“Wlodarczyk”的文本,看看它是否真的消失了。

你需要从流中移除“Wlodarczyk”这块木头。关闭警报根本解决不了问题。你只是让它安静下来,认为问题已经解决了吗?;)

因此,现在是使用另一个工具的时候了:

cin.ignore可以比作一辆带绳索的特殊卡车,它会来移除导致流堵塞的木头日志。它可以清除您的程序用户所创建的问题。

那么,在警报响起之前,我们能使用它吗?

可以:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

在第7行制造噪音之前,“Wlodarczyk”将被删除。

10000和'\n'是什么?

它表示删除10000个字符(以防万一),直到遇到'\n'(ENTER)。顺便说一句,使用numeric_limits可以更好地完成这项任务,但这不是本答案的主题。


所以问题的主要原因在噪音产生之前就已经消失了...

那么我们为什么还需要“清除”呢?

如果有人在第6行问“告诉我你的年龄”而不是写20,会怎样呢?

类型又不匹配了。计算机试图将字符串分配给整数。警报开始响起。你甚至没有机会对这种情况做出反应。在这种情况下,cin.ignore也无法帮助你。

因此,在这种情况下,我们必须使用clear:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

但是你是否应该“以防万一”清除状态?

当然不需要。

如果出现了问题(比如 cin >> age;),指令将返回 false 来通知你。

因此,我们可以使用条件语句来检查用户是否在流中输入了错误的类型。

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

好的,我们可以像这样解决我们最初的问题:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

当然,这可以通过使用while循环来改进,例如在问题中所做的。

奖励:

你可能会想知道。如果我想从用户那里获取姓名和姓氏并将它们放在同一行中,该怎么办?如果cin将每个由“空格”分隔的值解释为不同的变量,那么这是否可能呢?

当然可以,有两种方法:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) 或者使用getline函数。

getline(cin, nameOfStringVariable);

这就是如何做到的:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

第二个选项可能会反噬你,如果你在使用getline之前使用了'cin'。

让我们来看一下:

a)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

如果您将年龄设为“20”,则不会要求输入姓名和姓氏。
但是,如果您这样做:
b)
string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

一切都很好。

什么?!

每次你在输入流上放置一些东西时,你都会留下一个空格符,即ENTER('\n')在末尾。所以如果数据来自用户,则必须以某种方式输入到控制台。

b)cin的特点是忽略空格,因此当您从cin中读取信息时,换行符'\n'并不重要。它被忽略了。

a)getline函数获取整个行直到换行符('\n'),当换行符是getline函数获取的第一件事时,getline函数获取'\n',这就是全部内容。您提取了由在第3行将“20”放入流中的用户留下的换行符。

因此,为了修复它,每次使用cin获取任何值时,如果您将在程序中使用getline(),则始终调用cin.ignore()。

因此,正确的代码应该是:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

希望现在流更清晰了。

哈哈请让我安静!:-)


2
我不是 OP,我知道这已经过去几年了,但那非常有启发性。谢谢! - Ali

2

使用cin.ignore(1000,'\n')可以清除缓冲区中之前cin.get()读取的所有字符,并且它会在遇到'\n'或前1000个字符时停止。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接