8.1 IO类

IO库类型和头文件

39378a97302be7a6d50ec11588bc52d6.png :::info 此外,为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据。宽字符版本的类型和函数的名字以一个w开始。e.g., wcin :::

IO类型间的关系

概念上,设济类型和字符大小都不会影响我们要执行的IO操作。例如,我们可以用>>读取数据,而不用管是从一个控制台窗口,一个磁盘文件,还是一个string读取。类似的,我们不用管读取的字符能存入一个char对象内,还是需要一个wchar_t对象来存储。 :::info 标准库使我们能忽略这些不同类型的流之间的差异,这是通过继承机制(inheritance)实现的。 :::

IO对象无拷贝或者赋值

:::tips 我们不能拷贝或对IO对象赋值 :::

由于不能拷贝IO对象,因此我们也不能将形参或返冋类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。

条件状态

:::tips IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。可以通过条件状态来查看和操作IO流的状态。 ::: | strm::iostate | strm是一种IO类型。iostate是一种机器相关的类型,提供了表达条件状态的完整功能 | | —- | —- | | strm::badbit | strm::badbit用来指出流已经崩溃 | | strm::failbit | strm::failbit用来指出一个IO操作失败了。 | | strm::eofbit | strm::eofbit用来指出流到达了文件结束。 | | strm::goodbit | 用来指出流未处于错误组安泰,此值保证为零。 | | s.eof() | 若流s的eofbit置位,则返回true | | s.fail() | 若流s的failbit或badbit置位,则返回true | | s.bad() | 若流s的badbit置位,则返回true | | s.good() | 若流s处于有效状态,则返回true | | s.clear() | 将流s中的所有条状状态位复位,将流的状态设备为有效,返回void | | s.clear(flags) | 根据给定的flags标志位,将流s中对应的条件状态位复位。flags的类型为strm::iostate。返回void | | s.setstate(flags) | 根据给定的flags标志位,将流s中对应的条件状态位置位。flags的类型为strm::iostate。返回void | | s.rdstate() | 返回流s的当前条件状态,返回值类型为strm::iostate |

:::info 一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用 :::

  1. while(cin>>num){
  2. //...
  3. }

管理输入输出缓冲

每个输出流都管理一个缓冲区,用来保存程序读写的数据。 :::info 文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。 ::: 导致缓冲刷新(即,数据真正写到输出设輅或文件)的原因有很多: :::tips

  1. 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
  2. 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
  3. 我们可以使用操纵符如endl(参见1.2节,第6页)来显式刷新缓冲区。
  4. 在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下对cerr设置是unitbuf的,因此写到cerr的内容都是立刻刷新的。
  5. 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。例如,默认情况下,cincerr都关联到cout。因此,读cin或写cerr都会导致cout的缓冲区被刷新。
    1. 当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。
    2. 我们既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream,需要使用tie操作符cin.tie(&cout) :::

      人为刷新

      IO库中还有两个类似的操纵符:flushendsflush刷新缓冲区,但不输出任何额外的字符:ends向缓冲区插入一个空字符,然后刷新缓冲区
      1. cout<<"hi!"<<endl; //输出hi和一个换行,然后刷新缓冲区
      2. cout<<"hi!"<<flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
      3. cout<<"hi!"<<ends; //输出hi和一个空字符,然后刷新缓冲区

      unitbuf操作符

      如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接卜来的每次S操作之后都进行一次flush操作。而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制
      1. cout<<unitbuf;
      2. cout<<nounitbuf;
      :::warning 如果程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。 :::

      8.2 文件输入输出

      文件IO类

      :::tips 头文件fstream定义了三个类型来支持文件IO:
  • ifstream从一个给定文件读取数据;
  • ofstream向一个给定文件写入数据;
  • fstream可以读写给定文件。 ::: 84ebe0335b49eae30261e3bb021e0aa0.png

    使用文件流对象

    当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。

    1. ifstream in(ifile);
    2. ofstream out; //未与任何文件关联

    open&close

    如果我们定义了一个空文件流对象,可以随后调用open来将它与文件关联起来

    1. out.open(ifile+".copy");
  • 如果调用open失败,failbit会被置位。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯。

  • 一旦一个文件流己经打幵,它就保持与对应文件的关联。实际上,对一个已经打开的文件流调用**open**会失败,并会导致**failbit**被置位。随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭己经关联的文件。一旦文件成功关闭,我们可以打开新的文件
  • 当一个fstream对象被销毁时,close会自动被调用

    文件模式

    每个流都有一个关联的文件模式(filemode),用来指出如何使用文件:
    229b0f7bd81f6bd5cdc985c59dbf8f9e.png :::warning

  • 只可以对ofstream或fstream对象设定out模式。

  • 只可以对ifstream或fstream对象设定in模式。

默认情况下,当 我 们 打 幵 一 个ofstream时,文件的内容会被丢弃。阻止—个ofstream清空给定文件内容的方法是同时指定app模式 :::

8.3 string流

sstream头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样 :::info istringstream从string读取数据,ostringstream向string写入数掘,而头文件stringstream既可从string读数据也可向string写数据。 :::

小结

  • IO库分三种:标准IO:iostream,文件IO:fstream,string流IO:sstream。三者操作类似
  • 类对象没法拷贝或者赋值,(属于是单例模式?)
  • 类会出现错误状态,可以通过条件状态来查看和操作IO流的状态。
  • IO类会自己有缓冲区,在特定情况下需要手动刷新flush,ends,endl,unitbuf