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

首先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 

using namespace std;

int main(int argc, char const *argv[]) {
    int ID;
    int score;
    //定义一个100字节的字节数组缓存
    char name[100];

    cout<<"Enter ID:"<>ID;  //-

    cout<<"Enter name:"<>name;

    cout<<"Enter score:"<>score;

    cout<<"You input data as follow..."<

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

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

int ID;
cin>>ID;

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

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

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

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

...//前面的代码省略
  cout<<"Enter ID:"<>ID;

    //我们在这里使用C++的条件状态方法,检测一下
    if(cin.fail()){
        cout<<"cin.fail status:"<

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

  • 当前流一旦发生错误 (状态检测不通过), 其上后续该流的 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!!"<

流的绑定

  • C++的标准库默认将cin流和cout关联在一起
  • 一个istream对象可以关联到另一个ostream,也可以让一个ostream关联到另一个ostream
  • 每个流同时最多关联到一个流,但多个流可以同时关联到同一个ostream
#include 

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流)