4.2 管理条件状态
  IO类库提供了3个函数来管理和设置流的状态:
  s.clear(); // 将流s中所有条件状态复位,将流的状态设置为有效,调用good会返回true
  s.clear(flags); // 根据给定的flags标志位,将流s中对应的条件状态复位
  s.setstate(flags); // 根据给定的flags标志位,将流s中对应的条件状态置位。
  s.rdstate(); // 返回一个iostate值,对应流当前的状态。
  我们可以这样使用上面的这些成员函数。
  iostream::iostate old_state = cin.rdstate(); // 记住cin当前的状态
  cin.clear(); // 使用cin有效
  process_input(cin); // 使用cin
  cin.setstate(old_state); // 将cin置为原有状态
  cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); // 下failbit和badbit复位,保持eofbit不变。
  5,IO缓冲区 5.1 输入缓冲
  我们先看一个简单的输入输出程序:
  int main()
  {
  char ch;
  while (cin >> ch && ch!='#')
  {
  cout << ch;
  }
  return 0;
  }
  程序的功能是,循环输入字符,然后把输入的字符显式出来,遇到#或cin流失败时结束,按照程序的表面来看,我们想要的效果是输入一个,显示一个,像这样rroonnyy#,红色代表的是显示的结果。而实际中我们的输出与输出却是这样的:
  ronny#abc [Enter]
  ronny
  输入字符立即回显是非缓冲或直接输入的一个形式,它表示你所键入的字符对正在等待的程序立即变为可用。相反,延迟回显是缓冲输入的例子,这种情况下你所键入的字符块被收集并存储在一个被称为缓冲区的临时存储区域中。按下回车键可使你输入的字符段对程序起作用。
  缓冲输入一般常用在文本程序内,当你输入有错误时,可以使用你的键盘更正修正错误。当终按下回车键时,你可以发送正确的输入。
  而在一些交互性的游戏里需要非缓冲输入,如:游戏里你按下一个键时要执行某个命令。
  缓冲分为两类:
  1)完全缓冲:缓冲区被充满时被清空(内容发送到其目的地)。这种类型的缓冲通常出现在文件输入中。
  2)行缓冲:遇到一个换行字符时被清空缓冲区。键盘的输入是标准的行缓冲,因此按下回车键将清空缓冲区。
  5.2 输出缓冲
  上面讲的是输入的缓冲,而C++中的输出也是存在缓冲的。
  每个输出流都管理一个缓冲区,用来保存程序读写的数据。例如,如果执行下面的代码
  os<<”please enter a value:”;
  文本串可能立即打印出来,但也有可能被操作系统保存在缓冲区中,随后再打印。有了缓冲机制,操作系统可以将程序的多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
  导致缓冲刷新(即,数据真正写到输出设备或文件)的原因有很多:
  <1> 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。
  <2> 缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。
  <3> 我们可以使用操纵符endl来显式刷新缓冲区。
  <4> 在每个输出之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。
  <5> 一个输出流被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新,cin和cerr都关联到cout。因此读cin或写cerr会导致cout的缓冲区被刷新。
  除了endl可以完成换行并刷新缓冲区外,IO库中还有两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出,任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。
  cout << "hi!" << endl; // 输出 hi 和一个换行符,然后刷新缓冲区
  cout << "hi!" << flush; // 输出hi,然后刷新缓冲区,不附加任何额外字符
  cout << "hi!" << ends; // 输出hi和一个空字符。然后刷新缓冲区
  如果想每次输出操作后都刷新缓冲区,我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作后都进行一次flush操作。而nounitbuf操作符则重置流使其恢复使用正常的系统管理的缓冲区刷新机制。
  cout << unitbuf; // 所有输出操作后都立即刷新缓冲区
  // 任何输出都立即刷新,无缓冲
  cout << nounitbuf; // 回到正常的缓冲方式
  注意:如要程序异常终止,输出缓冲区是不会被刷新的。当一个程序崩溃后,它所输出的数据很可能停留在输出缓冲区中等待打印。
  我们可以将一个istream流关联到另一个ostream,也可以将一个ostream流关联到另一个ostream。
  cin.tie(&cout); // 标准库已经将cin与cout关联在一起
  // s.tie如果s关联到一个输出流,则返回指向这个流的指针,如果对象未关联到流,则返回空指针
  ostream *old_tie = cin.tie(nullptr); // 将cin不再与其他流关联,同时old_tie指向cout
  cin.tie(&cerr);  // 读取cin会刷新cerr而不是cout
  cin.tie(old_tie);  // 重建cin和cout的正常关联
  6,使用文件流 6.1 使用文件流对象
  创建一个文件流对象时,我们可以提供文件名,也可不提供文件名,后面用open成员函数来打开文件。
  string infile="../input.txt";
  string outfile = "../output.txt";
  ifstream in(infile); // 定义时打开文件
  ofstream out;
  out.open(outfile); // 用open打开文件
  如果调用open失败,failbit会被置位,所以调用open时进行检测通常是一个好习惯。
  如果用一个读文件流ifstream去打开一个不存在的文件,将导致读取失败,而如果用一写文件流ofstream去打开一个文件,如果文件不存在,则会创建这个文件。
  一旦一个文件流已经被打开,它保持与对应文件的关联。实际上,对一个已经打开的文件流调用open会失败,并会导致failbit被置位,随后的试图使用文件流的操作都会失败。为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。关闭一个流的关联文件可以用close成员函数来完成。
  6.2 文件模式
  每个流都有一个关联的文件模式,用来指出如何使用文件,下面列出了文件模式和它们的含义:
  in
  以读方式打开
  out
  以写方式打开
  app
  每次写操作均定位到文件末尾
  ate
  打开文件后立即定位到文件末尾
  trunc
  截断文件
  binary
  以二进制方式进行IO
  用文件名初始化一个流时或用open打开文件时都可以指定文件模式,但要注意下面几种限制:
  <1>只可以对ofstream或fstream对象设定out模式。
  <2>只可以对ifstream或fstream对象设定in模式。
  <3>只有out设定时才可以设定trunc模式。
  <4>只要trunc没有被设定,可以设定app模式。在app模式下,即使没有显式指定out模式 ,文件也总是以输出方式被打开。
  <5>默认情况下,即使我们没有指定trunc,以out模式打开的文件也会被截断。为了保留以out模式打开的文件的内容,我们必须同时指定app模式,这样只会将数据追加写到文件末尾;或者同时指定in模式,即打开文件同时进行读写操作。
  <6>ate和binary模式可以用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
  以out模式打开文件会丢弃已有数据,所以阻止一个ofstream清空给定文件内容的方法是同时指定app模式。
  // 在这几条语句中,file1都被截断
  ofstream out("file1"); // 隐含以输出模式打开文件并截断文件
  ofstream out2("file1", ofstream::out); // 隐含地截断文件
  ofstream out3("file1", ofstream::out | ofstream::trunc); // 显式截断文件
  // 为了保留文件内容,我们必须显式指定app
  ofstream app("file2", ofstream::app); // 隐含为输出模式
  ofstream app("file2", ofstream::app | ofstream::out);