美文网首页IT狗工作室
第一篇:深入分析C++的I/O流

第一篇:深入分析C++的I/O流

作者: 铁甲万能狗 | 来源:发表于2019-12-30 11:50 被阅读0次

    首先I/O流在任何一门静态语言中他们都占有举足轻重的地方。我们先来了解一下三个的基本概念。


    C++ I/O 类继承示意图

    关于这个层次结构你可能首先注意到的是它使用了多重继承(我们告诉你要尽可能避免这种情况)。 但是,iostream库已经过设计和广泛测试,以避免任何典型的多重继承问题,因此您可以自由使用它而不必担心。

    标准输入、标准输入和标准错误

    从操作系统原理的角度理解,他们都属于文件 ,例如从Unix/Linux的角度理解都可以认为任何块设备(包括逻辑上的块设备)和硬件设备:键盘,显示器等...),从编成语言的角度,他们就是负责数据进/出的抽象接口,C++定义的标准流使程序员不用理会具体底层的硬件,而是基于逻辑上的一个数据接口进行编程。

    • 标准输入:在C++预设的cin对象用于保存来自数据生产者的输入,例如键盘,当程序当前不期望任何输入时,用户可以按下的回车键。而不是忽略用户后续的按键,数据被放入输入流,在那里它将等待程序准备好它。
    • 标准输出:在C++预设的cout对象用于接受来自输入流的数据,在cout对象真正向输出设备写入(显示)字节数据之前,字节数据会预先缓存在输出缓冲区之中。每个输出流都负责管理一个输出缓冲区(下文会提及)例如,当程序将数据写入其输出流时,打印机可能仍在预热。数据将位于输出流中,直到打印机开始使用它。
    1. cin: 标准输入继承自istream类,读取的字符立即写入输出缓存区
    2. cout:标准输出继承自ostream类,提供缓存输出
    3. cerr:标准错误继承自ostream类,提供无缓冲输出
    4. clog:标准日志继承自ostream类,提供缓冲输出

    标准输入的行为机制

    这里先跑一下下面一段代码,这段代码是一个很好的反面示例,你能够找得出多少个潜在的问题?

    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char const *argv[]) {
        int ID;
        int score;
        //定义一个100字节的字节数组缓存
        char name[100];
    
        cout<<"Enter ID:"<<endl;
        //从标准输入读取的字节数据缓存到整形
        cin>>ID;  //-
    
        cout<<"Enter name:"<<endl;
        cin>>name;
    
        cout<<"Enter score:"<<endl;
        cin>>score;
    
        cout<<"You input data as follow..."<<endl;
        cout<<"ID:"<<ID<<endl;
        cout<<"Name:"<<name<<endl;
        cout<<"Score:"<<score<<endl;   
        return 0;
    }
    

    一、提取操作符>>和变量存储问题

    现在来对这两条代码做个分析:

    int ID;
    cin>>ID;
    

    首先,第一个语句声明一个名为ID的int类型的变量,第二个语句从cin中提取要存储在其中的值。此操作使程序等待来自cin对象的输入;通常,这意味着当程序执行到提取操作符">>"将等待用户使用键盘输入一些字节序列。在这种情况下,请注意使用键盘输入的字符仅在按下ENTER(或RETURN)键时才会传送到变量.

    其次,提取运算符"**>>**"会根据运算符后面的变量类型来确定如何解释从cin对象中读取的字符

    • 如果它是整数类型,则期望的格式是一系列的数字;
    • 如果它是字符数组,则是一系列的字符;

    正如您所看到的,从cin中提取似乎使得从标准输入获取输入的任务变得非常简单。 但这种方法也有很大的缺点。 如果用户输入了无法解释为整数的其他内容,现在我们基于上面的示例在cin>>ID;这条语句加入一个条件状态语句fail(),查看一下我们的当前cin对象的流状态是否异常。

    ...//前面的代码省略
      cout<<"Enter ID:"<<endl;
        cin>>ID;
    
        //我们在这里使用C++的条件状态方法,检测一下
        if(cin.fail()){
            cout<<"cin.fail status:"<<cin.fail()<<endl;
        }
    ....//后面的代码省略
    

    那么,当我们故意输入一些与当前变量类型不符的字符,和预料之中的提取操作失败。

    • 当前流一旦发生错误 (状态检测不通过), 其上后续该流的 IO 操作都会失败
    • 默认情况下,C++程序会跳过剩下与该(异常状态)的流有关提取操作有关的语句,执行剩余其他的操作,显然这不是我们期望的.
      输入流提取操作符失败示例

    这是非常糟糕的程序行为,大多数程序都应以预期的方式运行,并适当地处理无效值. 这个示例也告诉我们,在处理输入流的时候执行I/O状态检测非常必要(关于I/O状态检测,后面单独开篇陈述)

    while(!cin.fail()){
         //业务代码
    }
    或
    while(cin>>变量){
        //业务代码
    }
    

    稍后我们将看到如何使用字符串流来更好地控制用户输入。

    二、cin读取空白字符串问题

    在上面的示例代码中,除了name一栏我们故意输入有空白字符,例如"peter yang ",其他输入保持正确.你看看我们最终的name输出是什么?没错!!

    输入空白字符,提取操作符失败示意图
    cin仅读取了空格字符之前的字符序列.另外,还会发现程序会跳过后续跟该输入流相关的提取操作.
    • 自cin对象从键盘读取字节序列时,直到它检测到空白字符('\t','\r','\n' , ' '等)会提取符会停止提取后续输入的字符序列 ,并将fail状态位设置为1,然后C++程序会跳过剩下与该(异常状态)的流有关提取操作的语句.
    • 以上行为,对于输出类同理适用.这里就不用示例代码验证了.

    I/O流状态检测

    I/O操作必定会伴随客户的行为,计算机的应用环境等因素,可能会出现错误,因此在在执行I/O操作的时候,对I/O状态检测是必须的.

    C++

    刷新缓冲区

    sss.gif

    每个输出流都管理一个缓冲区,用来保存成学读写的数据,导致刷新缓冲区的原因如下:

    一、程序正常结束,缓冲刷新被执行.
    二、 缓冲区满时,需要刷新缓冲区,新的数据才能继续写入缓冲区.
    三、 程序员使用了以下操作符,强制刷新缓冲区.
    • 执行到endl操作符,输出并刷新缓冲区内的数据最后输出一个换行符.
    • 执行到flush操作符,输出缓冲区的数据并执行刷新(不附加任何额外字符)
    • 执行到ends操作符,刷出缓冲区的数据并执行刷新,最后输出一个空字符.
    • 执行到unitbuf操作符,该操作符号告知输出流每次写操作之后都执行一次flush操作.
    • 一个输出流被关联到另外一个流,当读写被关联的流时,关联的流的缓冲区会被刷新.
    //输出并刷新缓冲区内的数据最后输出一个换行符.
            cout<<"itdog!!"<<endl; 
            //输出缓冲区的数据并执行刷新(不附加任何额外字符)
            cout<<"itdog!!"<<flush;
            //执行到ends操作符,刷出缓冲区的数据并执行刷新,
            //最后输出一个空字符.
             cout<<"itdog!!<<ends;
    
            //所有输出操作后都会立即刷新缓冲区
            cout<<unitbuf;
    
            //任何输出都立即刷新,无缓冲
            cout<<"itdog!!XXX";   
            //回到正常的缓冲方式
            cout<<nounitbuf;
    

    流的绑定

    • C++的标准库默认将cin流和cout关联在一起
    • 一个istream对象可以关联到另一个ostream,也可以让一个ostream关联到另一个ostream
    • 每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream
    #include <iostream>
    
    ostream my_s=ostream();
    ostream *old_tie=cin.tie(nullptr);
    
    istream is1=istream();
    istream is2=istream();
    
    is1.tie(&cout);
    is2.tie(&cout);
    
    cin.tie(&cerr);
    cin.tie(old_tie);
    
    

    相关文章

      网友评论

        本文标题:第一篇:深入分析C++的I/O流

        本文链接:https://www.haomeiwen.com/subject/haceectx.html