Kyle Chen's Blog

Action speaks louder than Words

0%

【C++】C++中的IO类总结——上篇

引入

1
2
3
4
5
6
7
8
#include <iostream>

int main(){

std::cout<<"Hello World!"<<std::endl;

}

  • 我们在学习C++时,往往都是从上面这段程序开始的
    也就是在控制台窗口上打印Hello World

  • 但是现在回过头去看这段简单的程序
    你有没有思考过
    cout代表什么?
    iostream代表什么?
    endl又代表了什么?
    <<运算符的作用是什么?
    为什么这样的操作就可以在屏幕上打印”Hello World”?
    等等问题

  • 看似简单的代码,背后却蕴含着庞大的知识体系

  • 之前一直对这些问题不太清晰
    现在重新回过头来总结一下

概念

C++的标准库中有专门用来进行IO操作的一种类,叫IO类,也叫输入输出流
通过这些IO类可以实现控制台IO,文件IO,内存IO
也就是向控制台/文件/内存写入数据,
以及从控制台/文件/内存读取数据,

分类

  • IO类一共有9中

  • 在iostream.hpp中定义了ostream istream iostream这三个类

      (1)istream是用于从控制台读取内容的流类,cin就是该类的对象
      
      (2)ostream是用于把内容输出到控制台的流类,cout就是该类的对象.
      
      (3)iostream是既能用于从控制台读取,又能把内容输出到控制台的类。
    
  • 在fstream.hpp中定义了ofstream ifstream fstream这三个类

      (1)ifstream是用于从文件读取数据的类.
      
      (2)ofstream是用于向文件写人数据的类.
      
      (3)fstream是既能从文件读取数据,又能向文件写人数据的类,
    
  • 在sstream.hpp这个头文件中 定义了stringstream类 ostringstream 类 istringstream类

      (1)istringstream是用于从内存读取数据的类.
      
      (2)ostringstream是用于向内存写人数据的类.
      
      (3)stringstream是既能从内存读取数据,又能向内存写入数据的类,
    

下面两张图展示了不同的IO类之间的继承关系

在这里插入图片描述

在这里插入图片描述

特性

    1. 不能对IO对象赋值或者拷贝。
1
2
3
4
5
ofstream out1,out2;
out1 = out2; //错误:不能对流对象赋值
ofstream print (ofstream); 错误:不能初始化ofstream参数
out2 = print (out2); 错误:不能拷贝流对象

  • 2.函数参数和返回值

      由1得知 ,当函数的参数或者返回值使用了IO对象类型,不能采用值传递,
      不能采用const 引用方式传递 , 
      只能采用非const 引用方式传递
    
    1. 错误处理

    在通过IO类进行读取的时候 不可避免会出现一些错误,比如文件格式错误 ,输入了错误的格式等等,
    C++中定义了一种叫 strm::iostate的bitset来表示对IO类读取操作时的不同状态 包含以下四种状态:

    1
    2
    3
    4
    5
    6
    7
    1.strm::goodbit:流处于有效状态。该位为 0 表示流没有出现任何错误。
    2.strm::failbit:由于格式或类型错误,读写操作失败。
    例如,从流中读取的值无法转换为有效的目标类型。该位为 1 表示发生了此类错误。
    3.strm::eofbit:已经读到流的末尾,即无法继续读取数据。
    该位为 1 表示读取操作已到达流的末尾。
    7.strm::badbit:流发生严重的错误,无法恢复。例如,数据无法从磁盘读取或写入磁盘,
    或者与底层设备的通信失败。该位为 1 表示发生了此类错误。

    以上四种条件状态可以按位组合使用,例如 s.fail() | s.eof() 表示流可能发生了读取失败或到达末尾的情况。
    1
    同时C++的IO类提供了下面这些接口进行状态判定和状态清除

    1.s.eof():判断输入流 s 是否读到文件末尾,即文件读取是否结束。如果已经到达文件结尾,就返回 true,否则返回 false。
    2.s.fail():判断是否在读取或写入流的过程中出现了错误,例如读取了无效的数据类型。如果流发生了 failbit 或 badbit 错误,则返回 true,否则返回 false。
    3.s.bad():判断流是否发生了不可恢复的错误,例如在程序运行时无法打开文件或者无法从流中读取数据。如果流发生了 badbit 错误,则返回 true,否则返回 false。
    4.s.good():判断流是否处于有效状态,即没有发生任何错误。如果流处于有效状态,则返回 true,否则返回 false。
    5.s.clear():将流 s 的所有条件状态位都复位,将流的状态设置为有效。该函数没有参数,返回值为 void。
    6.s.clear(flags):根据给定的 flags 标志位,将流 s 中对应条件状态位复位。flags 的类型为 strm::iostate,可以使用 | 运算符同时设置多个标志位。该函数没有返回值。
    7.s.setstate(flags):根据给定的 flags 标志位,将流 s 中对应条件状态位置位。flags 的类型为 strm::iostate,可以使用 | 运算符同时设置多个标志位。该函数没有返回值。
    8.s.rdstate():返回流 s 的当前条件状态,

下面是使用实例
在这里插入图片描述

在这里插入图片描述

  • 4.缓冲区机制

    什么是缓冲区机制?

      当我们在用std::cout的时候
      如果这样写的话std::cout<<"Hello World";
      那么当程序执行完这句时 屏幕上可能不会打印Hello world 也可能会打印
      这是因为每个输出流都管理一个缓冲区,用来保存程序读写的数据。
      当你执行上面的cout时,
      内容有可能被操作系统保存在缓冲区中,随后再打印。
      也有可能刚好缓冲区满了然后立刻打印
    

    为什么要有缓冲区机制?

      有了缓冲机制,
      操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
      由于操作系统级的写操作可能很耗时,
      允许操作系统将多个输出操作组合
     为单一的设备写操作可以带来很大的性能提升。
    

    缓冲区什么时候会刷新?

    1.程序正常结束
    程序异常结束,输出缓冲区不会被刷新
    在这里插入图片描述
    2.缓冲区满时

    3.使用endl/flush/ends来强制刷新
    在这里插入图片描述
    4.进行unitbuf设置

    注意 默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
    在这里插入图片描述

    ·5.一个输出流可能被关联到另一个流。
    在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。
    例如,默认情况下,cin和cerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。