Txing

欢迎来到 | 伽蓝之堂

0%

CPP Programming | Chapter 7:深入IO

Chapter 7 深入IO

IOStream概述

  • IOStream采用流式I/O而非记录I/O,但可以在此基础上引入结构信息

  • 所处理的两个主要问题

    • 表示形式的变化:使用格式化/解析在数据的内部表示与字符序列之间转换
    • 与外部设备的通信:针对不同的外部设备(终端、文件、内存)引入不同的处理逻辑
  • 所涉及到的操作(输出流序列)

    • 格式化/解析
    • 缓存
    • 编码转换
    • 传输
  • 采用模板来封装字符特性,采用继承来封装设备特性

    • 常用的类型实际上是类模板实例化的结果

      1
      2
      #include <fstream>
      std::ifstream x;

输入与输出

  • 输入与输出分为格式化和非格式化两类

  • 非格式化I/O:不涉及数据表示行驶的变化

    • 常用输入函数:get / read / getline / gcount
    • 常用输出函数:put / write
  • 格式化I/O:使用移位操作符来进行输入(>>)与输出(<<)

    • c++通过操作符重载以支持内建数据类型的格式化I/O
    • 可以通过重载操作符以支持自定义类型的格式化I/O
  • 格式控制

    • 可以接收位掩码类型(showpos)、字符类型(fill)与取值相对随意(width)的格式化参数

      • ```c++ std::cout.setf(std::ios_base::showpos); // 输出如果是正数,自动加正号 std::cout<< x <<std::endl; std::cout.width(10); // 输出10个字符,不足10个的,前面用空格替代. std::cout.fill('.'); // 用'.'替代上一条语句中设定的空格
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        - 注意width方法的特殊性:触发后被重置

        - 操作符

        - 简化格式化参数的设置
        - 触发实际的插入与提取操作

        - 提取会放松对格式的限制`cin<<x;`

        - 提取C风格字符串时要小心内存越界`setw(10)避免内存越界`

        - ```c++
        #include <iomanip>
        cout<< showpos << setw(10) << setfill('.') << x;
  • cout<< showpos << x;

文件与内存操作

  • 文件操作

    • basic_ifstream / basic_ofstream / basic_fstream

      1
      2
      3
      4
      5
      6
      7
      #include <iostream>
      #include <fstream>
      int main()
      {
      std::ofstream outFile("my_file"); // 输出流
      outFile << "Hello\n"
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include <iostream>
      #include <fstream>
      int main()
      {
      std::ifstream inFile("my_file"); // 输入流
      std::string x;
      inFile >> x;
      std::cout << x << '\n';
      }
    • 文件流可以处于打开/关闭两种状态,处于打开状态时无法再次打开,只有打开时才能I/O

      1
      2
      3
      .is_open()
      .open()
      .close()
  • 文件流的打开模式

    • ``` in 打开以供读取 out 打开以供写入 ate 表示起始位置位于文件末尾 app 附加文件,即总是项文件尾写入 trunc 截断文件,即删除文件中的内容 binary 二进制模式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      - 每种文件流都有缺省的打开方式

      - 注意ate和app的异同

      - binary能静止系统特定的转换

      - 避免意义不明确的流使用方式(如ifstream+out)

      - 合法的打开方式组合

      ```c++
      in 只读打开
      out | trunc 如果文件存在,将长度截断为0;否则建立文件以供写入
      out 如果文件存在,将长度截断为0;否则建立文件以供写入
      out | app 附加;打开或建立文本文件,仅供文件末尾写入
      in | out 打开文本文件供更新使用(支持读写)
      in | out | trunc 如果文件存在i,将长度截断为0;否则建立文件供更新使用
  • 内存流:basic_istringstream/basic_ostringstream/basic_stringstream

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <iostream>
    #include <sstream>
    int main()
    {
    std::ostringstream obj1;
    obj1 << 1234;
    auto res = obj1.str();
    std::out<< res << std::endl;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <iostream>
    #include <sstream>
    int main()
    {
    std::ostringstream obj1;
    obj1 << 1234;
    auto res = obj1.str();

    std::istringstream obj2(res);
    int x;
    obj2 >> x;
    std::out<< x << std::endl;
    }
  • 也会受打开模式影响(in/out/ate/app)

  • 使用str()方法获取底层所对应的字符串

    • 小心避免使用str().c_str()的形式获取C风格字符串
  • 基于字符串流的字符串拼接优化操作

    1
    2
    3
    4
    5
    6
    std::ostringstream obj;
    obj << "Hello";
    obj << " world";
    obj << "Hello";
    obj << " world";
    std::cout << obj.str() << std::endl;

流的状态

  • iostate

    • failbit / badbit / eofbit / goodbit
  • 检测流的状态

    • good() / fail() / bad() / eof() 方法
    • 转换为bool值(参考cppreference)
  • 注意区分fail与eof

    • 可能会被同时设置,但二者含义不同
    • 转换为bool值时不会考虑eof
  • 通常来说,只要流处于某种错误状态时,插入/提取操作就不会生效

  • 复位流状态

    • clear:设置流的状态为具体的值(缺省为goodbit)
    • setState:将某个状态附加到现有的流状态上
  • 捕获流异常:exceptions方法

流的定位

  • 获取流的位置

    • tellg() / tellp() 可以用于获取输入 / 输出流的位置(pos_type类型)
    • 两个方法可能会失败,此时返回 pos_type(-1)
  • 设置流位置

    • seekg() / seekp() 用于设置输入 / 输出流的位置(覆盖而非插入)
    • 这两个方法分别有两个重载版本
      • 设置绝对位置:传入 pos_type 进行设置
      • 设置相对位置:通过偏移量(字符个数 ios_bose::beg)+ 流位置符号的方式设置
        • ios_base::beg
        • ios_base::cur
        • ios_base::end
  •   #include <sstream>
      #include <iostream>
      int main()
      {
        std::ostringstream os("hello, world");
        os.seekp(7);
        os << 'W';
        os.seekp(0,std::ios_base::end);
        os << '!';
        os.seekp(0);
        os << 'H';
        std::cout << os.str() << '\n';
      }

流的同步

  • 基于flush() / sync() / unitbuf 的同步
    • flush() 用于输出流同步,刷新缓冲区
      • std::flush; cout.flush();
    • sync() 用于输入流同步,其实现逻辑是编译器所定义的
    • 输出流可以通过设置 unitbuf 来保证每次输出后自动同步
  • 基于绑定(tie)的同步
    • 流可以绑定到一个输出流上,这样在每次输入 / 输出前可以刷新输出流的缓冲区
    • 比如:cin 绑定到了cout上
  • 与C语言标准IO库的同步
    • 缺省情况下,C++的输入输出操作会与C的输入输出函数同步
    • 可以通过sync_with_stdio关闭该同步