我们来看下下面这段代码:
#include
#include
#include
int main()
{
int num = 0;
std::vector<int> ivec;
do {
std::cout << "please input some numbers:" << std::endl;
while (std::cin >> num)//一直检测输入流状态知道遇到文件结束符(ctrl+z)或错误输入
ivec.push_back(num);
if (ivec.size() == 0)
std::cout << "Error!" << std::endl;
} while (ivec.size() == 0);
system("pause");
return 0;
}
现在我们基于这段代码进行一些讨论:
①
运行发现,当第一次输入正确的时候,则运行正确,如果第一次输入错误(例如以Ctrl+z然后按Enter),则程序会进入死循环:
分析以下原因:
首先需要了解cin的用法。C++输入缓冲机制规定当用户键入输入之后按下Enter键,便会将所有刚刚用户输入的一次性全送到缓冲区,而cin便会从输入缓冲区中读取数据。回车标志一次输入的完成,如果数据不够,则会等待用户继续输入;如果数据有多余,则将多余的数据存储在输入流缓冲区中,供下次使用。
举个例子:
while(std::cin >> num)//一直检测输入流状态知道遇到文件结束符(ctrl+z)或错误输入
std::cout<< num << std::endl;
这条语句,如果输入一次1 2 3 ,执行结果是:
1
2
3
执行过程如下:
第一次运行std::cin >> num
的时候,输入缓冲区是空的,当用户输入1 2 3 ctrl+z然后回车,这时候cin读入第一个整数1,然后输出1和换行,下一次执行 std::cin >> num
的时候,缓冲区不为空,所以不再要求用户输入,直接读取第二个整数2,然后输出2和换行。以此类推,后面输出3 。
然后cin检查到文件结束标志ctrl+z,cin>>num返回false,循环退出。
需要特别注意的一点是:当缓冲区中有残留数据时,cin会直接去读取缓冲区的数据而不会请求键盘输入。重要的是,回车符也会被存在输入缓冲区中。
有了这些知识,就可以解释前面代码中的现象了。如果第一次没有输入有效字符,以ctrl+z加上回车键结束输入后,回车符会被当成一个字符存入输入缓冲区。循环的时候,到了while(std::cin >> num)
的时候,cin读取到文件结束符(Ctrl+z),cin的状态被置位,不再接收输入,所以ivec.size()
等于0,程序再次进入下一个循环,由于 cin不再接受输入, `所以程序进入死循环。(之前看别人说是Enter的原因,但其实不是,在cin进行第二次读取之前,cin已经不再接受输入)
所以我们需要将代码做一点小小的修改:
int main()
{
int num = 0;
std::vector<int> ivec;
do {
std::cout << "please input some numbers:" << std::endl;
while (std::cin >> num)//一直检测输入流状态知道遇到文件结束符(ctrl+z)或错误输入
ivec.push_back(num);
std::cin.clear();//添加这一行。
if (ivec.size() == 0)
std::cout << "Error!" << std::endl;
} while (ivec.size() == 0);
system("pause");
return 0;
}
修改后的代码运行正常
代码中添加了std::cin.clear();
,这个函数有两个作用:
i、用来更改cin的状态标示符的,cin在接收到错误的输入的时候,会设置状态位good。如果goodbit位不为0,则cin不接受输入。如果下次输入前状态位没有改变那么即使清除了缓冲区数据流也无法输入。所以当输入流发生错误后,要想再次进行输入,必须添加std::cin.clear()。
ii、具有清除缓冲区的作用。
std::cin.ignore()这个函数不具备更改cin状态标识的功能,如果把上面代码中的std::cin.clear()函数换成std::cin.ignore,则程序在第一次输入不正确的情况下仍会进入死循环:
②
接着讨论第二个问题,把第二段代码修改以下:
int main()
{
int num = 0, val1 = 0;
std::vector<int> ivec;
do {
std::cout << "please input some numbers:" << std::endl;
while (std::cin >> num)//一直检测输入流状态知道遇到文件结束符(ctrl+z)或错误输入
ivec.push_back(num);
std::cin.clear();
if (ivec.size() == 0)
std::cout << "Error!" << std::endl;
} while (ivec.size() == 0);
std::cin >> val1;
std::cout << val1;
system("pause");
return 0;
}
运行后输入1 2 3 Ctrl+z 然后Enter,直接就这样了:
程序不会等待你输入,直接输出了val1的初始值0,这是为什么呢?
前面说过:当缓冲区中有残留数据时,cin会直接去读取缓冲区的数据而不会请求键盘输入。而且重要的是,回车符也会被存在输入缓冲区中。
程序运行后输入1 2 3 Ctrl+z然后Enter,while(std::cin >> num)一直检测输入流知道cin读取到文件结束符(Ctrl+z),循环结束,1、2、3都会被依次cin读取并从缓冲区中除去,留下一个Enter还在缓冲区中等待输入流,所以执行std::cin >> val1 这个语句的时候,cin会去缓冲区中读取数据,读取到Enter后输入结束,所以cout直接输出val1的初始值0。
所以我们需要将代码进行以下修改:
int main()
{
int num = 0, val1 = 0;
std::vector<int> ivec;
do {
std::cout << "please input some numbers:" << std::endl;
while (std::cin >> num)//一直检测输入流状态知道遇到文件结束符(ctrl+z)或错误输入
ivec.push_back(num);
std::cin.clear();
if (ivec.size() == 0)
std::cout << "Error!" << std::endl;
} while (ivec.size() == 0);
std::cin.ignore();//添加这一句
std::cin >> val1;
std::cout << val1;
system("pause");
return 0;
}
在while循环之后添加std::cin.ignore(),在执行std::cin >> val1前,清除缓冲区中的内容,则程序运行正常。
但这里有产生一个问题,在用户输入的时候多次输入Enter,程序却不会出现输入错误,一直等待着用户输入,直到有正常数据输入:
这里值得说一下的是,当执行cin语句时,Enter可能会被当成是连续的空格符来处理,也可能被当成是结束符来处理,这个由cin的定义决定,而作为连续的空格则被忽略掉,
我们可以来测验最前面所说的一下造成程序进入死循环的原因,
在上面那段代码中std::cin.ignore后面再添加一句while( std::cin >> num );
如图所示,while(std::cin >> num)在接收到文件结束符后便不再接受输入,直接输出val1、val2的初始值0。如果说是里面残留了一个Enter的原因的话,那val1结束输入有可能,但两个同时结束输入就不太对了吧。
四个也一样:
加了std::cin.ignore()清除掉残留在缓冲区的Enter也不行:
所以一开始程序进入死循环的原因,是cin读取到文件结束符(Ctrl+z),cin的状态被置位,不再接收输入,所以ivec.size()
一直等于0,进入死循环,就是这样。
以上内容其实可以总结成三点:
1、当cin接收到错误输入后,其状态会被改变,不再接收输入,只有用cin.clear()将cin的goodbit复位之后,才能正常接收输入。
2、输入时键入的Enter也会被存储在缓冲区中。
3、Enter有时候会被读成连续的空格,有时候会被读成结束符,这个由cin自己判断决定。