C++中cin的剖析

输入原理:

程序的输入都建有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin函数直接从输入缓冲区中取数据。正因为cin函数是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin函数会直接取得这些残留数据而不会请求键盘输入。

C++中的cin是一个 istream对象,从标准输入中读取数据,在iostream头文件中定义。

cin提取输入流遇到空格会不会结束的问题

“流提取符 >> 从流中提取 非字符数组或者字符串 数据时通常跳过流中的空格 tab键换行符等空白字符。”

#include <iostream>
using namespace std;
​
int main()
{
int a,b;
​
cin>>a>>b;
cout<<a<<"  "<<b<<endl;
return 0;
}
结果见下图:

“用cin>> 读取 字符数组或者字符串 数据时遇到空白字符(包括空格 tab键和回车)作为终止字符。”

#include <iostream>
using namespace std;
int main()
{
    char a[20];
    cin>>a;
    cout<<a;
    return 0;
}
结果见下图:

注:第一句是指在读之前会跳过这些,第二句是指流的结束标志

流对象不能拷贝或赋值。此外,读写一个IO对象都会改变其状态,因此传递和返回的引用都不能是const的,否则无法读写。

条件状态:

IO流有四种条件状态,分别用位来控制。

cin.badbit : 001 1 表示系统级错误,一旦被置位,流就无法再使用了

cin.eofbit : 010 2 表示流已经读完,到达尾部了

cin.failbit: 100 4 可恢复错误,如期望读取数值却读出一个字符等错误,或者已经到达流的尾部

cin.goodbit: 000 0 可用状态

当一个流遇到数据类型不一致的错误而不可用时,我们可以使其恢复为有效状态(置换eof和fail位)

cin.clear( cin.rdstate() & ~cin.failbit & ~cin.badbit ); //cin.rdstate()表示流当前的状态

cin对象有几个专门用来报告其工作情况的成员函数,他们将返回一个真/假值来表明cin的状态。

-eof():如果到达文件(或输入)末尾,返回true;

-fail():如果cin无法工作,返回ture;

-bad():如果cin因为比较严重的原因(例如内存不足)而无法工作,返回true;

-good():如果以上情况都没发生,返回true;

cin从缓冲区读取数据,有多种方式,如操作符 >> 函数getline()、get()等


  1. >>

根据后面的变量的类型读取数据

读取时结束条件:enter, space, tab

对结束符的处理:丢掉

​
#include <iostream>
using namespace std;
int main()
{
   char str1[10], str2[10];
   cin>>str1;
   cin>>str2;
   cout<<str1<<endl;
   cout<<str2<<endl;
   return 0;
}

当遇到类型不一致时,流处于不可用状态,若需继续使用这个流,需恢复流的有效状态。

2.get

输入结束条件:换行符

对结束符处理:不丢掉

#include <iostream>  
using namespace std;  
int main()  
{  
  char c1, c2;  
   cin.get(c1);  
   cin.get(c2);  
    cout<<c1<<" "<<c2<<endl; // 打印两个字符  
    cout<<(int)c1<<" "<<(int)c2<<endl; // 打印这两个字符的ASCII值  
    return 0;  
}

输入a b,结果c1被赋值为a, c2被赋值为空格。即get函数只会从缓冲区中取字符,而不会过滤掉任何空格换行符等。(可自己运行只输入a和换行符的情况)

#include <iostream>
 
int main()
{
    int c1, c2;
    c1=cin.get();
    c2=cin.get();
    cout<<c1<<" "<<c2<<endl; // 打印两个字符
    cout<<(int)c1<<" "<<(int)c2<<endl; // 打印这两个字符的ASCII值
 
    cin >> c1;
    cin >> c2;
    cout<<c1<<" "<<c2<<endl; // 打印两个字符
    cout<<(int)c1<<" "<<(int)c2<<endl;
    return 0;
}

测试:输入123,再输入4的结果。cin.get()只是读取字符,即使输入数字,也是char型,再转换为int型。第一次c1,c2被赋值为'1'和'2',此时缓冲区还有一个3和换行符,cin流仍然有效,再次调用cin流,给c1赋值为缓冲区的3,过滤掉换行符,再次调用cin时,缓冲区已经没有字符了,则从键盘读取,提示用户输入,输入4赋值给c2.此时的c1和c2直接是int型的。

123

49 50

49 50

4

3 4

3 4

int main() 
{ 
        char cstr;
        char bstr;
         cstr=cin.get();        //读取单个字符,在屏幕输入
        bstr=cin.get();  
         cout<<cstr<<bstr<<endl;  //输出刚刚载入的单个字符
        system("pause"); 
}

我们再输入:abcd 最后输出了:ab 既然cin.get()是读取第一个字符,那bstr为什么不也是a呢? 其实原理是这样的: 在cin这个对象里,有一个储存字符的流,可以想象成缓冲区,但事实上是cin里封装的一个东西.当我们在程序上输入字符后,对象cin获得了我们输入的字符,例如获得abcd,然后再通过.get()把流里面的第一个字符去掉,赋给cstr,这时,cin里储存的流的数据为bcd,而cstr则获得了a.当我们再次运行bstr=cin.get();时,同理把cin里流的数据的b拿出来给了bstr,此后,cin里面的流的数据为cd,而bstr则为b,所以最后输出时,便能输出ab了.

还有个补充,究竟什么时候才输入数据呢?我们可以再通过上面的代码进行尝试,我们输入单个字母'a',然后按回车,发现并没有输出数据,而是再等待一次输入数据,我们再输入字母'b',按回车后便输出ab了.相信到这里,大家都应该明白了,因为当我们第一次输入a后,通过cstr=cin.get();使cin里的流没有数据,清空了.所以到第二次要再赋给bstr值时,它找不到数据,要重新再输入数据.由此来看可以知道,当cin里的流数据清空时,便需要重新输入才能赋值.而cin.get()还有个用法:

int main() 
{ 
        char cstr;
        char bstr;
         cstr=cin.get();        //读取单个字符,在屏幕输入
        cin.get();
        bstr=cin.get();  
         cout<<cstr<<bstr<<endl;  //输出刚刚载入的单个字符
        system("pause"); 
}

程序中有3个cin.get(),所以我们尝试输入:abc. 发现输出了:ac 由此能知道,当空回调cin.get();时,cin.get便自动在cin中的流数据中删除一个字母,起了一个删除作用.

对cin.get()有了一定了解之后,对cin.getline()的学习就可以更快了,原理是一致的,但是cin.getline()则是获取一整行文本.以下是cin.getline()原形:

3.getline()

getline(char *line,int size,char '/n') 第一个就是字符指针,第二个是字符长度,第三个1行的结束标识符.

int main() 
{ 
        char cstr[200];
        cin.getline(cstr,sizeof(str));     //第三个参数不输入,默认回车为结束标识符
        cout<<cstr<<endl;                //输出
        system("pause"); 
}

这样我们输入一堆英文或数字,然后按回车,就会输出一行刚刚输出的东西了.接下来.我们讨论第三个参数的作用.

int main() 
{ 
        char cstr[200];
        cin.getline(cstr,sizeof(str),'X');     //我们以单个英文字母'X'作为终止标识符
         cout<<cstr<<endl;                     //输出
        system("pause"); 
}

当我们输入一大堆东西,例如

输入: kkkkkkk(回车)                                 输出: kkkkkkk(回车)

         bbbbbbb(回车)                                         bbbbbbb(回车)

         lllllX                                                           lllll

这样X便成了终止符,其原理和cin.get一样.或许我们可以像cin.get那样尝试一下:

int main() 
{ 
        char cstr[200];
        char bstr[200];
        cin.getline(cstr,sizeof(str),'X');     //我们以单个英文字母'X'作为终止标识符
        cin.getline(bstr,sizeof(btr),'a');
         cout<<"第一行是:"<<cstr<<endl;                     //输出
        cout<<"第二行是:"<<bstr<<endl;  
        system("pause"); 
}

我们输入:

kkkkkkkkk(回车)                输出:第一行是:kkkkkkkkk(回车)

oooooooooX(回车)                                   ooooooooo(回车)

bbbbbbbbba(回车)                    第二行是:(回车)

                                                                  bbbbbbbbb

在这里,我在不厌其烦地说一下原理,如果刚刚cin.get()原理看懂的可以跳过. 首先,我们第一次getline会把X前面的字符赋给cstr,然后从cin里的数据流删除,标识符X也删除了,所以输出的cstr如上所示.当我们第二次运行getline时,此时cin里的数据流为(回车)bbbbbbbbba,回车也是一个字符,事实上在数据流里用"/n"表示,接着就按照原来第一次的方法,把标识符'a'前面的字符赋给bstr,然后再删除字符号及标识符.所以输出结果如上.

接下来我们谈谈cin.clear的作用,第一次看到这东西,很多人以为就是清空cin里面的数据流,而实际上却与此相差很远,首先我们看看以下代码:

#include <iostream> 
using namespace std; 
int main() 
{ 
        int a; 
        cin>>a; 
        cout<<cin.rdstate()<<endl; 
        if(cin.rdstate() == ios::goodbit)
{
cout<<"输入数据的类型正确,无错误!"<<endl; 
             } 
        if(cin.rdstate() == ios_base::failbit) 
        { 
                cout<<"输入数据类型错误,非致命错误,可清除输入缓冲区挽回!"<<endl; 
        } 
        system("pause"); 
}

我们定义要输入到的变量是整型,但如果我们输入了英文字母或者汉字,那就会发生错误,cin里有个方法能检测这个错误,就是cin.rdstate(); 当cin.rdstate()返回0(即ios::goodbit)时表示无错误,可以继续输入或者操作,若返回4则发生非致命错误即ios::failbit,则不能继续输入或操作.而cin.clear则可以控制我们此时cin里对这个问题的一个标识.语发如下: cin.clear(标识符); 标识符号为:

goodbit 无错误 Eofbit 已到达文件尾 failbit 非致命的输入/输出错误,可挽回 badbit 致命的输入/输出错误,无法挽回

若在输入输出类里.需要加ios::标识符号

通过cin.clear,我们能确认它的内部标识符,如果输入错误则能重新输入.结合真正的清空数据流方法cin.sync(),请看下例:

#include <iostream> 
using namespace std; 
int main() 
{ 
        int a; 
        while(1) 
        { 
                cin>>a; 
                if(!cin)            //条件可改写为cin.fail() 
                { 
                        cout<<"输入有错!请重新输入"<<endl; 
                        cin.clear(); 
                         cin.sync();   //清空流
                } 
                else 
                { 
                        cout<<a; 
                        break; 
                } 
        } 
        system("pause"); 
}

上面的cin默认参数为0,即无错误,正常操作.当我们输入英文字母'k'时,它的状态标识改为fail(即1),即错误,用cout对用户输出信息,再用cin.clear让错误标识改回为0,让我们可以继续输入,再清空流数据继续输入.如果我们没有了cin.clear,则会进入死循环,其过程为我们输入了英文字母,它的状态标识便为fail,当运行到条件判断时,便总是回到错误的条件表示里,并且我们再也没办法输入,因为错误的表示关闭了cin,所以会进入死循环.

---------------------------------------------------------------------

自己再添加一句:如果输入错误,则再也输入不进去,须用clear.而sync用于清除当前输入缓冲区中的内容。

\#include <iostream>
​
int main()
{
 using namespace std;
 int a; 
 cin >> a;
 cout << a <<endl;
 cin >> a ;
 cout <<a <<endl;
 cin.clear();
 cin.sync();  // 可以将cin.clear();cin.sync();  不同时注释掉试一下就知道了
 cin >> a;
 cout <<a <<endl;
​
return 0;
​
}

getline读取一行,以换行符结束,丢掉换行符。还可指定读取多少个字符到数组,读取完后剩余的字符放在流中,流被置为无效状态,可以通过置换使他们变有效,然后继续读取,见例子。

#include <iostream>
int main()
{
    char str[10];
    cin.getline(str,5);
    cout << str << endl;
    cout << "read state: " << cin.rdstate() << endl;
    cin.clear(cin.rdstate() & ~cin.failbit);
    cout << "read state: " << cin.rdstate() << endl;
    cin.getline(str,5);
    cout << str << endl;
    return 0;
}

4.cin.read(c, n)

从字符串流中读取n个字符到c数组中。

char score[20]; 
cin.read(score,20); 

5.cin之后使用getline会出现空行,调用cin.ignore()即可

#include <iostream>
#include <string>
 
using namespace std;
int main()
{
    int n;
    cin >> n;
    cin.ignore(); //如果注释掉
    string c;
    getline(cin, c);
    cout << c << endl;
        return 0;
}

如果注释掉cin.ignore(),输入2\enter,输出空行。

不注释,输入2\enterA,输出A。

因为cin有时会以\n作为结束标志,但它还在缓存区中,而getline以\n为结束标志,会读取上一次输入得到的\n,得到一个空行。

从输入流(cin)中提取字符,提取的字符被忽略(ignore),不被使用。每抛弃一个字符,它都要计数和比较字符:如果计数值达到a或者被抛弃的字符是ch,则cin.ignore()函数执行终止;否则,它继续等待。

它的一个常用功能就是用来清除以回车结束的输入缓冲区的内容,消除上一次输入对下一次输入的影响。

比如可以这么用:

cin.ignore(1024,'\n'),通常把第一个参数设置得足够大,这样实际上总是只有第二个参数'\n'起作用,所以这一句就是把回车(包括回车)之前的所以字符从输入缓冲(流)中清除出去。

cin.ignore(1000, '\n')的含义是把缓冲区内从当前字符开始直到'\n'之前字符(如果有1000个的话)忽略掉,实际上你这里假设一行不会超过1000个字符,所以含义是忽略一行

6.cin.clear()、cin.sync()

cin.clear()是用来更改cin的状态标示符的。

cin.sync()是用来清除缓存区的数据流的。 如果标示符没有改变那么即使清除了数据流也无法输入。所以两个要联合起来使用。例如:

#include<iostream>
using namespace std;
​
int main()
{
 int a;
 cout<<"输入一个字母:"<<endl;
 cin>>a;  //int型变量中放了char型数据,failbit置1
 cout<<"cin.fail()="<<cin.fail()<<endl;    //输出1
​
 //cin.clear();
 //cin.sync();
 cout<<"输入一个数字:"<<endl;    //由于failbit值为1,输入流不能正常工作
 cin>>a;                         //故此处的输入无效
 cout<<a<<endl;                  //输出不确定值
​
 cin.clear();                    //此处用cin.clear()流标志复位
 //cin.sync();
 cout<<"cin.fail()="<<cin.fail()<<endl;        //此处failbit已为0
​
 cout<<"输入一个数字:"<<endl;
 //但刚才输入的字符并没有从流中清除,所以cin>>a又把那个字符放入a中,流输入流又不能正常工作
 cin>>a;
 cout<<a<<endl; //输出不确定值
 cout<<"cin.fail()="<<cin.fail()<<endl;    //在此处failbit又为1
​
 cin.clear();            //再次修复输入流
 cin.ignore();            //取走刚才流中的字符
 cout<<"输入一个数字:"<<endl;    //再次接收用记输入,这次输入数字,正常输出了
 cin>>a;
 cout<<"a="<<a<<endl;
 //现在再看一下输入流的failbit
 cout<<"cin.fail()="<<cin.fail()<<endl;//输出0,表明输入流已恢复正常
 return 0;
}

sync()的作用就是清除输入缓冲区。成功时返回0,失败时badbit会置位,函数返回-1. 另外,对于绑定了输出的输入流,调用sync(),还会刷新输出缓冲区。

但由于程序运行时并不总是知道外部输入的进度,很难控制是不是全部清除输入缓冲区的内容。通常我们有可能只是希望放弃输入缓冲区中的一部分,而不是全部。比如清除掉当前行、或者清除掉行尾的换行符等等。但要是缓冲区中已经有了下一行的内容,这部分可能是我们想保留的。这个时候最好不要用sync()。可以考虑用ignore函数代替。 cin.ignore(numeric_limitsstd::streamsize::max(),'/n');//清除当前行 cin.ignore(numeric_limitsstd::streamsize::max()); //清除cin里所有内容

不要被长长的名字吓倒,numeric_limitsstd::streamsize::max()不过是climits头文件定义的流使用的最大值,你也可以用一个足够大的整数代替它。

使用ignore显然能比sync()更精确控制缓冲区。

7.cin.putback(x)

将x内容置入缓冲区。

#include<iostream>
 
using namespace std;
 
int main(){
char a = ‘m’,b;     
cin.putback(a);
cin>>b; 
cout<<a<<endl;
cout<<b;
}

测试输入:无输入

输出:

m

m

【分析】将a中数据置入缓冲区后,直接流入b中。

8.cin.peek()

返回缓冲区中的下一个字符,但只是查看,并不从缓冲区中取出。

#include<iostream>
using namespace std;
 int main(){
char input[100]; 
char ch; 
int i=0; 
while((ch=cin.peek())!='.'&&ch!='\n') 
    cin.get(input[i++]); 
input[i]='\0'; 
}

【分析】程序遇到句号或换行符循环停止。句点或换行符仍停留在输入流中。可见,使用peek的效果相当于先用get()读取一个字符,再用putback()将字符放入输入流中。

9.in.width(长度)

接收长度-1个字符,其他的放在流中等待接收

#include<iostream>
using namespace std;
int main()
{
       int w=4;
       char str[10];
       cout<<"Entera sentence:\n";
       cin.width(5); //每次只接收4个字符,其他的放在流中等待接收
       while( cin>> str)
       {
              cout.width(w++);//将4个字符输出,设置每次输出的域宽增加1
             cout<<str<<endl; //输出字符
             cin.width(5);    //设置接收4个字符
       }
       return 0;
}

输入:

happy new year

输出:

happ

    y

   new

   year

^Z

【分析】app(四个字符),y留在下一次。遇到空格接收结束,第二次只有y,到y输出时,输出域宽是5。下一个接收的是new(后面的空格断开了接收),……

猜你喜欢

转载自blog.csdn.net/u011486738/article/details/82284581