What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

后一修订版
前一修订版
cs:programming:cpp:courses:cpp_basic_deep:chpt_7 [2024/11/11 06:52] – 创建 - 外部编辑 127.0.0.1cs:programming:cpp:courses:cpp_basic_deep:chpt_7 [2024/12/10 15:16] (当前版本) – [基于绑定的同步] codinghare
行 1: 行 1:
 +======深入IO======
 +//第 7 章笔记//
 +----
 +====IOStream 概述====
 +  * IOstream 把输入输出视作字节流(流式IO)
 +  * 可以在其基础上构建记录 IO
 +<WRAP center round box 100%>
 +  * 流式 IO
 +  * 记录 IO
 +</WRAP>
 +===需要处理的问题===
 +  * 表示形式的变化:使用格式化 / 解析将数据内部的表示(二级制)转化为字符序列(或反向转换,比如 ''std::cin''
 +  * 与外部设备的通信:对于不同的外部设备(''IOstream'' 指定的外部设备:比如终端,文件,内存)等会有不同的处理逻辑
 +<code cpp>
 +int x = 100;
 +// 通过格式化的方式,将二进制的形式转换成了相应的字符序列的表示
 +std::cout << x << std::endl;
 +</code>
 +<code cpp>
 +// 使用一块内存
 +// 用于保存 x 或者 y
 +union 
 +{
 +   int x;
 +   float y;
 +};
 +x = 100;
  
 +// 使用不同的方式去解析同一内存中的内容
 +// float 与 int 的解析方式不同
 +std::cout << x << std::endl;
 +
 +std::cout << y << std::endl;
 +</code>
 +===涉及的操作===
 +  * 格式化 / 解析:将内容转化为字节序列 / 将字节序列转化为内容
 +  * 缓存:将要输出的内容置于缓存中,在缓存将满的时候一次性输出
 +  * 编码转换:比如 UTF8 到 GB(转换字符所占内存的表示)
 +  * 传输:从缓存中逐一进行读取
 +==采用的技术==
 +  * ''std::basic_stream''
 +    * 封装设备特性:通过不断的继承,在每一步的继承中实现不同设备的输出
 +    * 封装字符特性:使用类模板中的参数,通过替换该参数实现不同的流(比如使用 ''std::basic_ifstream<char>'' 处理 ''char'' 类型字符)
 +    * 实际上是类模板实例化的结果
 +====输入和输出====
 +  * 输入输出分为格式化和非格式化两类
 +===非格式化 I/O===
 +  * 输入:''get'' / ''read''/ ''getline'' / ''gcount''
 +  * 输出: ''put'' / ''write''
 +  * 特点:对计算机(性能)友好,不会通过格式化来改变内容的长度(比如输出 ''float'' 就只会最多输出 4个字节的内容)
 +<code cpp>
 +int x;
 +//对指定地址的内容的字符进行读取
 +//不解析,直接放入 x
 +//一共读取 4个字符 100加上回车,对应的就是每个字符的 ascii 值
 +//注意:read 要求 4个字符,在没有满足条件之前,系统会一直等待用户输入
 +std::cin.read(reinterpret_cast<char*>(&x), sizeof(x));
 +</code>
 +===格式化 I/O===
 +  * 使用移位操作符 ''>>'' 和 ''<<'' 的重载来输入输出
 +  * 特点:对人友好,根据不同的类型(内建,自定义)进行**重载**,输出不同的格式:
 +<code cpp>
 +char c = '0';
 +// 针对 char 输出字符 0
 +std::cout << c << std::endl;
 +
 +// 针对 Int 输出 ascii 48
 +int ci = static_cast<int>(c);
 +std::cout << ci << std::endl;
 +</code>
 +==格式控制==
 +  * 位掩码类型:''showpos'',改变的是格式化的行为
 +<code cpp>
 +char c = '0';
 +// 显示正负,输出 +48
 +// 只对数值产生影响
 +std::cout.setf(std::ios_base::showpos);
 +</code>
 +  * 取值随意的格式化参数:''width()''
 +<code cpp>
 +// 让输出占10个字符,默认往左边加空格
 +// 默认只生效一次,读取后会被 reset 为 0
 +std::cout.width(10);
 +</code>
 +  * 填充字符 ''fill''
 +<code cpp>
 +// 使用 * 占位
 +std::cout.fill('*');
 +</code>
 +===操纵符===
 +  * manipulator:允许直接在输入输出流中使用格式控制:
 +<code cpp>
 +// 等同与之前的 showpos 用法,输出 +48
 +// witdh 的替换,需要使用 iomanip 头文件,使用 setw()
 +// fill 的替换,使用 setfill()
 +std::cout << std::showpos << std::setw(10) << std::setfill('*') << ci << std::endl;
 +</code>
 +==其他操纵符==
 +  * ''std::endl''
 +===输入相关===
 +  * 具有提取操作:
 +    * 会放松对提取的类型限制:比如使用 ''std::cin'' 时,空格+10,+10,+010 都会被提取为 ''10''
 +    * 提取操作有限制(某些信息不能智能进行提取)
 +==C风格字符串的内存越界==
 +<code cpp>
 +char y[5];
 +// 输入长度超过 4字节 (4+\0) 均会导致内存越界
 +// std::string 存在缓存机制,不受限制
 +std::cin >> y;
 +
 +// 使用 setw() 控制输入的长度,提取前 4 个字符
 +std::cin >> std::setw(5) >> y;
 +</code>
 +====文件与内存操作====
 +===文件流===
 +  * ''basic_ifstream'' / ''basic_ofstream'' / ''basic_fstream'':别名 ''i/f stream'',类模板,流中单位为 ''char''
 +  * 输入 / 输出 / 同时打开输入输出
 +==简单示例==
 +<code cpp>
 +// 传入参数为文件名
 +// 输出流
 +std::ofstream outFile("test_file");
 +// 输出到文件 test_file
 +outFile << "hello";
 +
 +// 输入流
 +std::ifstream inFile("test_file");
 +std::string x;
 +inFile >> x;
 +std::cout << x << std::endl;
 +</code>
 +==文件流状态==
 +  * 检测状态 ''std::basic_ostream::isopen()''
 +  * 打开文件,关联流:''outFile.open("test_file");''
 +  * 关闭文件,关闭流:''outFIle.close();''
 +<WRAP center round box 100%>
 +文件流会处于打开 / 关闭状态:
 +  * 当与文件关联时,才会处于打开状态
 +  * 关联文件之后,无法关联另外一个文件。必须要先关闭流才能与其他文件绑定(再次打开)
 +  * 缺省定义下(无文件参数),文件流默认关闭。
 +</WRAP>
 +==系统会使用缓存优化文件流==
 +  * 每次读取写入都会非常消耗资源
 +  * 系统会将缓存内的东西积累起来,在关闭流时写入文件
 +  * 未显示关闭文件流时,''ostream'' 的析构函数会在析构其对象时自动销毁
 +  * 可以使用域控制 ''ostream'' 对象的生存周期,使用其析构函数自动关闭文件流
 +<code cpp>
 +{
 +    // outFile 会在大括号之后自动关闭流
 +    std::ostream outFile.open("test_file");
 +}
 +</code>
 +===打开方式===
 +^ 标记名       ^ 作用                           ^
 +| ''in''       | 打开供读取                     |
 +| ''out''      | 打开供写入                     |
 +| ''ate''      | 起始位置位于文件末尾           |
 +| ''app''      | 附加文件,总是向文件末尾写入   |
 +| ''trunc''    | 截断文件,删除文件中内容       |
 +| ''binary''   | 二进制模式                     |
 +==组合使用的原理==
 +上述的打开方式都是以二进制的形态设计的,其中每一种打开方式占一位。如果组合使用,则是以两种方式的**按位或**运算进行:
 +<code cpp>
 +// in & ate
 +// 按位 或进行组合
 +std::ios_base::in;   // 0010
 +std::ios_base::ate; // 0001
 +// 得到 0011,在文件末尾进行读写
 +</code>
 +==ate 的使用==
 +  * 用于控制文件读取 / 写入的起始位置
 +  * ''in'' / ''out'' 默认起始为文件开始的位置
 +<WRAP center round box 100%>
 +  * ''ate'' 表示起始读写位置处于文件末尾,但该位置可以移动
 +  * ''app'' 表示起始读写位置处于文件末尾,但该位置**不可**移动
 +</WRAP>
 +==trunc 的使用==
 +  * 写入,并删除之前文件内的内容
 +<code cpp>
 +// 将文件中的 hello 替代为 word
 +std::ofstream outFile("test_file", std::ios_base::out | std::ios_base::trunc);
 +outFile << "word";
 +</code>
 +==binary==
 +  * 可以禁止系统内定的转换
 +==常用组合==
 +^打开方式^效果^加结尾标记^加二进制标记^
 +|''in''|只读打开(读)|初始位置位于末尾|禁止系统转换|
 +|''out|trunc / out''|文件存在则覆盖之前内容,否则建立文件(写)|初始位置位于末尾|禁止系统转换|
 +|''out|app''|在文件末尾写入(写)|初始位置位于末尾|禁止系统转换|
 +|''in|out''|打开文件供更新使用(读写)|初始位置位于末尾|禁止系统转换|
 +|''in|out | trunc''|打开文件,删除已存在内容,并建立文件更新使用(读写)|初始位置位于末尾|禁止系统转换|
 +
 +<WRAP center round box 100%>
 +''out|trunc'' 和 ''out'' 行为一致。
 +</WRAP>
 +===内存流===
 +都定义于 ''<sstream>'' 中:
 +  * ''basic_istringstream'':读
 +  * ''basic_ostringstream'':写
 +  * ''basic_stringstream'':读写
 +  * 处理类型单位默认为 ''char'',也受打开模式的影响(''in''/''out''/''ate''/''app'')
 +==基础用法示例==
 +  * 注意格式化输出会将输入转化为字符串:
 +<code cpp>
 +int main(int argc, char const *argv[])
 +{
 +    std::ostringstream myObj;
 +    // 写入内容到流 (整数)
 +    // 转换整数到字符串(格式化)
 +    myObj << 1234;
 +    // 获取 myObj 所对应内存,使用配套的 str() 返回内容(字符串)
 +    std::string ret = myObj.str();
 +
 +    std::cout << ret << std::endl;
 +    //读取字符串,并格式化(转换字符串为 int)
 +    std::istringstream myObj2(ret);   
 +    int x;  
 +    //将 int 赋值给 x 并打印
 +    myObj2 >> x;
 +    std::cout << x << std::endl;
 +    return 0;
 +}
 +</code>
 +==配合模式使用==
 +<code cpp>
 +// 默认的模式是从起始位置开始替换
 +
 +// 默认输出流中的内容是 test
 +// 所有输出都会基于 test 输出
 +std::ostringstream myObj3("test", std::ios_base::ate);
 +myObj3 << '1';
 +
 +// 打印结果为 test1
 +// stringstream 会自动管理内存
 +std::cout << myObj3.str();
 +</code>
 +==str() 的使用注意==
 +  * 返回 ''std::string''
 +  * 不要**间接**使用 ''str().c_str()'' 将字符串转换为 C 风格的字符串:该行为未定义
 +    * ''str()'' 返回的是右值
 +    * ''c_str()'' 指向了返回右值的内部的首地址,由于返回的是临时值,所以该访问未定义
 +==使用内存流进行拼接优化==
 +<code cpp>
 +// 类似 vector 的空间申请
 +// 可能每次都会进行 allocate
 +std::string s;
 +s += "hello";
 +s += "world";
 +s += "hello";
 +
 +// 改良
 +// 利用 ostringstream 的大缓存进行拼接
 +// 不会频繁的 allocate
 +std::ostringstream word;
 +word << "hello";
 +word << "world";
 +word << "hello";
 +
 +// 结果相同
 +std::cout << s;
 +std::cout << word.str();
 +</code>
 +====流的状态====
 +  * 提供额外的信息,使使用者针对状态进行对应的操作
 +  * 使用 ''std::io_base::iostate'' 维护状态
 +  * 非正常的状态的值都不是 ''0'',几种错误可以同时出现(通过 bit 之间的按位或, //BitMaskType//
 +===三种异常状态===
 +  * ''badbit'':不可恢复的流错误(比如读写未关联的流)
 +  * ''failbit'':可恢复的流错误:读 / 写类型不匹配且无法转换(格式化错误),双重关闭一个流
 +  * ''eofbit'':输入序列达到了文件尾部
 +==检测异常状态的方法==
 +[[https://en.cppreference.com/w/cpp/io/ios_base/iostate|bit 的组合与返回结果的关系]]
 +<code cpp>
 +// 使用 cin 的成员检测
 +std::cout << std::cin.good() 
 +          << std::cin.fail()
 +          << std::cin.bad()
 +          << std::cin.eof();
 +// 转换为 bool 值检测
 +          <<static_cast<bool>(std::cin) << std::endl;
 +</code>
 +  * ''fail'' 和 ''eof'' 可能会被同时设置,但意义不同
 +  * 转换为 bool 值时不会考虑 ''eof''
 +==利用状态==
 +<code cpp>
 +// 判断之前的输入提取是否成功
 +if(std::cin >> x) { //}
 +</code>
 +==复位流的状态==
 +  * ''clear()'' :设置当前流的指定状态(默认为 ''goodbit''
 +  * ''setstate()'':将某个状态**附加**到指定流上,
 +<code cpp>
 +std::cin.clear();
 +</code>
 +==捕获流的异常==
 +  * 可以通过 ''exception()'' 捕获流的异常
 +====流的定位====
 +提取写入时,需要考虑提取写入的位置(定位)。C++ 为此引入了流的定位:
 +  * 获取流
 +  * 设置流
 +===获取流位置===
 +  * ''tellg()'':获取输入流中,接下来要读取的字符的位置
 +  * ''tellp()'':获取输出流中,当前可以写入的位置(读的是 white space)
 +  * 两者返回一个为整数的 ''pos_type'' 类型。获取失败时,返回 ''pos_type(-1)''
 +    * ''fail() == true''
 +===设置流位置===
 +  * ''seekg()'' / ''seep()'':用于设置输入 / 输出流(覆盖输出)
 +==两个重载版本==
 +  * 设置绝对位置:接收 ''pos_type'' 的版本
 +  * 设置相对位置:接收基本位置(''beg'',''end'',''cur'')+ 位移
 +====流的同步====
 +系统默认的 “缓冲区满再输入到设备的” 行为很可能带来一些问题:
 +<code cpp>
 +// 如果 some test 所在流没有满,导致没有被输出到终端
 +std::cout << "some test";
 +std::string name;
 +// 导致输入时没有提示
 +std::cin >> name;
 +</code>
 +这种情况下需要**刷新缓冲区**,也就是强制输出缓冲区内容送到设备上。
 +===基于方法的同步===
 +  * ''flush()'':**输出**流同步,刷新缓冲区
 +<code cpp>
 +// 成员调用
 +std::cout.flush();
 +// 操纵符
 +std::cout << x << std::flush;
 +</code>
 +  * ''sync()'':**输入**流同步,实现由编译器定义
 +    * 同文件同时关联了输入输出流,要对输入流刷新,得到输出流的信息
 +    * 具体行为与编译器实现有关系
 +  * ''std::unitbuf''
 +    * 行为:大于一个字符的流必须被清除出缓存区
 +    * 结果:自动立即刷新(影响性能)
 +    * 通常与 ''std::cerr'' (标准错误输出)联用,因为错误信息必须及时更新
 +===基于绑定的同步===
 +C++ 中,任意流都可以绑定到一个**输出流**上。当绑定时,绑定流会记录被绑定流中的信息。当绑定时:
 +  * 绑定流在每次输入(输出)时,都会刷新**被绑定流**的缓冲区
 +  * 绑定流可以同时与多个输出流绑定
 +这种方法实际上从另外的角度解决了之前提到过的问题:
 +<code cpp>
 +// 如果 some test 所在流没有满,导致没有被输出到终端
 +std::cout << "some test";
 +std::string name;
 +// 导致输入时没有提示
 +std::cin >> name;
 +</code>
 +此时如果 ''std::cin'' 绑定了 ''std::cout'',那么当 ''std::cin'' 输入时,就会直接情况输出流的缓存,将我们需要的 //some test// 
 +文本输入终端。
 +===与 C 标准 I/O 的同步===
 +  * 缺省情况下会与 C 同步
 +  * 可以通过 ''sync_with_stdio'' 关闭同步