问题描述:输入数据时希望有个合法性检验的问题,如果输入的数据不合法则程序提示重新输入。最初代码如下:
#include <iostream>
#include <vector>
using std::cin ;
using std::cout ;
using std::endl;
using std::vector ;
int main()
{
int num;
vector<int> ivec;
do
{
cout<<"please enter some numbers:"<<endl;
while(cin>>num)
ivec.push_back(num);
if(ivec.size()==0)
cout<<"Error!"<<endl;
}while(ivec.size()==0);
//其他语句
}
这样写如果第一次正确输入,程序运行正确。但如果第一次没有输入有效字符(比如输入ctrl+z,回车)或者第一个输入为非法字符,则程序陷入死循环,不停的输出提示信息,但是不允许用户再次输入。
正确写法:
#include <iostream>
#include <vector>
using std::cin ;
using std::cout ;
using std::endl;
using std::vector ;
int main()
{
int num;
vector<int> ivec;
do
{
cout<<"please enter some numbers:"<<endl;
while(cin>>num)
ivec.push_back(num);
//增加了两行
cin.clear();
cin.sync(); //或者用cin.ignore();
if(ivec.size()==0)
cout<<"Error!"<<endl;
}while(ivec.size()==0);
}
增加上面两行之后,可以实现预定功能,程序运行正常。
说明原因:
首先需要了解cin的用法。程序的输入都有一个缓冲区,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin函数直接从输入缓冲区中读取数据。这种缓冲机制规定,只有收到回车键时,才会将所有输入的数据一次性提交到cin函数。回车标志一次输入的完成,如果数据不够,则会等待用户继续输入;如果数据有多余,则将多余的数据存储在输入流缓冲区中,供下次使用。举个栗子:
while(cin>>num)
cout<<num<<endl;
这条语句,如果输入一次1 2 3 4 ,执行结果是:
1
2
3
4
执行过程如下:
第一次运行cin>>num的时候,输入缓冲区为空,所以会显示下划线让用户输入。用户输入1 2 3 4 ctrl+z,回车,这时候cin读入第一个整数1,然后输出1和换行。
下一次执行 cin>>num的时候,缓冲区不为空,所以不再要求用户输入,直接读取第二个整数2,然后输出2和换行。以此类推,依次输出3 ,4。
然后cin检查到结束标志ctrl+z,cin>>num返回false,循环退出。
需要特别注意的一点是:当缓冲区中有残留数据时,cin函数会直接去读取这些残留数据而不会请求键盘输入。而且,回车符也会被存入输入缓冲区中。
有了这些知识,就可以解释原始代码中的现象了。如果第一次没有输入有效字符,以ctrl+z加上回车键结束输入后,回车符会被当成一个字符存入输入缓冲区。下一次再运行时,到了while(cin>>num)这一步,由于缓冲区中已有一个回车符,所以cin函数直接去缓冲区中读取回车符,而不允许用户再次输入数据,这就是第一次非法输入后不能再次输入数据的原因。由于需要的数据num是int型的,显然读取到的回车符仍然是非法的,程序再次进入下一个循环,并且一直这样循环下去,这就是程序陷入死循环原因。
解决办法:
从上面的分析我们可以看出,导致问题的根本是输入缓冲区没有及时清空,以至于前一次的输入影响了后面的输入。因此,在输入前加上清空输入缓冲区的语句就可以解决问题。c++用于清空输入缓存的函数有三个:cin.clear(),cin.sync(),cin.ignore()。
cin.clear()是用来更改cin的状态标示符的,cin在接收到错误的输入的时候,会设置状态位good。如果good位不为1,则cin不接受输入,直接跳过。如果下次输入前状态位没有改变那么即使清除了缓冲区数据流也无法输入。所以清除缓冲区之前必须要cin.clear()。
cin.ignore(a,ch):从输入缓冲去中提取字符,提取的字符被忽略,不被使用。每抛弃一个字符,它都要计数和比较字符,如果计数值达到a或者被忽略的字符是ch,则cin.ignore()函数终止执行。默认参数是a=1,即仅忽略缓冲区中的第一个字符。它的一个常用功能是用来清除以回车结束的输入缓冲区的内容,消除上一次输入对下一次输入的影响。比如cin.ignore(1024,' '),通常把第一个参数设置得足够大,这样实际上总是只有第二个参数起作用,所以这一句就是把回车(包括回车)之前的所有字符从输入缓冲区中清除。
cin.sync()的作用是清除输入缓冲区全部的内容。
另外,函数fflush(stdin)的功能也是清空输入缓冲区,但是此函数并不是在c/c++标准中定义的,它仅适用于部分编译器(如VC),并非所有的编译器都支持这个功能。经过实验,在visual studio中用这个函数无法清空输入缓冲区。