1. IOStream 概述(I:输入,O:输出)
●IOStream 采用流式 I/O 而非记录 I/O (类似于数据库),但可以在此基础上引入结构信息
● 所处理的两个主要问题:
– 表示形式的变化:使用格式化 / 解析在数据的内部表示与字符序列间转换
– 与外部设备的通信:针对不同的外部设备(终端、文件、内存)引入不同的处理逻辑
● 所涉及到的操作
– 格式化 / 解析
– 缓存
– 编码转换
– 传输
● 采用模板来封装字符特性,采用继承来封装设备特性
– 常用的类型实际上是类模板实例化的结果
eg: ifstream,使用模板逐步细化

2. 输入与输出
输入与输出分为格式化与非格式化两类
**A、非格式化 I/O :**不涉及数据表示形式的变化,非格式化对人类阅读很不友好
– 常用输入函数: get(一个) / read (多个)/ getline(一行) / gcount(缓存里剩余的)
– 常用输入函数: put(一个) / write(多个)
B、 格式化 I/O : 使用移位操作符来进行的输入 (>>) 与输出 (<<)
– C++通过操作符重载以支持内建数据类型的格式化 I/O
– 可以通过重载操作符以支持自定义类型的格式化 I/O
● 格式控制
setf()
– 可接收位掩码类型( showpos 修改某一位 )、字符类型( fill() 填充参数字符)与取值相对随意( width() ,可以接收整数,加参数个空格)的格式化参数
– 注意 width() 方法的特殊性:触发后被重置
补充:关于showpos :
在C++中,showpos是一个用于控制输出流的格式标志,它在输出正数时显示加号(+),而在输出负数时显示负号(-)。它主要用于调整输出的格式,使得输出的数值更易读。
要使用showpos标志,你需要包含<iostream>头文件,并使用std::showpos函数或使用std::ios_base::showpos成员函数来设置输出流的格式标志。
下面是一个使用showpos的简单示例:
#include <iostream>
int main() {
int number = 10;
double value = -3.14;
std::cout << std::showpos; // 设置输出流的格式标志为showpos(+-)
std::cout << number << std::endl; // 输出:+10
std::cout << value << std::endl; // 输出:-3.14
return 0;
}
在上面的示例中,通过设置std::cout的showpos标志,输出的整数number在前面显示了一个加号,而输出的浮点数value在前面显示了一个负号。
需要注意的是,showpos标志仅影响整数和浮点数类型的输出,对于其他类型(如字符串、字符等)则不起作用。另外,showpos标志是一个永久性的标志,即一旦设置后,后续的输出将一直保持该格式,除非通过其他方式取消设置。
如果你想取消showpos标志的设置,可以使用std::noshowpos函数或std::ios_base::noshowpos成员函数来清除输出流的格式标志。例如:
std::cout << std::noshowpos; // 取消showpos标志的设置
这样,后续的输出将不再显示加号或负号。
● 操纵符
– 简化格式化参数的设置
– 触发实际的插入与提取操作
setw()函数是定义在头文件中的一个函数,用于设置输出流中字段的宽度。
注意:setw()函数触发后被重置 ,只影响其后的输出项,即只对紧随其后的输出产生作用。如果想要持久设置输出流的字段宽度,可以使用std::setfill()函数。
另外,需要包含头文件才能使用setw()函数。
● 提取会放松对格式的限制,例如输入 ‘+a’只输出‘+’;
● 提取 C 风格字符串时要小心内存越界:

避免方法:可以使用setw()设置边界:

3. 文件与内存操作
A、 文件操作
- basic_fstream:
basic_ifstream(读取)/basic_ofstream(写入)
ifstream和ofstream是C++标准库中用于文件输入和输出的两个类。它们都是从基类fstream派生而来。 - 文件流可以处于打开 / 关闭两种状态,处于打开状态时无法再次打开,只有打开时才能 I/O。
- 当使用std::ofstream outFile(“my_file”); 这种方式时,不必进行打开关闭动作,程序会自动打开关闭的。或者使用一个域。
一般都是先写入缓存,再写到终端/文件中去
B、 文件流的打开模式
– 每种文件流都有缺省的打开方式
– 注意 ate 与 app 的异同
– binary 能禁止系统特定的转换
– 避免意义不明确的流使用方式(如 ifstream + out )
缺省的打开例子:
C、六种文件流的打开模式:
in | out 读写文件时,打开文件一般位于文件开头;
ate:起始位置位于文件末尾,这个位置可以移动
app: 起始位置位于文件末尾,这个位置不可以移动
D、合理的文件打开组合:

E、内存流相关
● 内存流: basic_istringstream / basic_ostringstream / basic_stringstream
● 也会受打开模式: in / out / ate / app 的影响
● 使用 str() 方法获取底层所对应的字符串
– cpp风格的字符串.c_str()是合理的
– 小心使用 str().c_str() 的形式获取 C 风格字符串,以下为指向一块已经释放的内存,即未定义:

● 基于字符串流的字符串拼接优化操作

4. 流的状态、定位与同步
A、流的状态
●iostate
– failbit / badbit / eofbit / goodbit:
● 检测流的状态
– good( ) / fail() / bad() / eof() 方法
– 转换为 bool 值(参考cppreference)

从级别来看:fail() < bad()
● 注意区分 fail 与 eof
– 可能会被同时设置,但二者含意不同
– 转换为 bool 值时不会考虑 eof
eof 表示到了尾部的位置
● 通常来说,只要流处于某种错误状态时,插入 / 提取操作就不会生效
● 复位流状态
– clear :设置流的状态为具体的值(缺省为 goodbit )
– setstate :将某个状态附加到现有的流状态上
● 捕获流异常:exceptions方法

B、流的定位
● 获取流位置
– tellg() / tellp() 可以用于获取输入 / 输出流位置 (pos_type 类型 )
– 两个方法可能会失败,成功时为当前可以输入的位置指示器,若流失败、异常时候会出现失败则为 pos_type(-1)
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream s;
std::cout << s.tellp() << '\n';
s << 'h';
std::cout << s.tellp() << '\n';
s << "ello, world ";
std::cout << s.tellp() << '\n';
s << 3.14 << '\n';
std::cout << s.tellp() << '\n' << s.str();
}
输出:
0
1
13
18
hello, world 3.14
● 设置流位置
– seekg() / seekp() 用于设置输入 / 输出流的位置
– 这两个方法分别有两个重载版本:
● 设置绝对位置:传入 pos_type 进行设置
● 设置相对位置:
通过偏移量(字符个数 ios_base::beg ) + 流位置符号的方式设置
– ios_base::beg
– ios_base::cur
– ios_base::end
seekg():设置当前关联 streambuf 对象的输入位置指示器。
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::string str = "Hello, world";
std::istringstream in(str);
std::string word1, word2;
in >> word1;
in.seekg(0); // 回溯
in >> word2;
std::cout << "word1 = " << word1 << '\n'
<< "word2 = " << word2 << '\n';
}
C、流的同步
● 基于 flush() / sync() / unitbuf 的同步
– flush() 用于输出流同步,刷新缓冲区
– sync() 用于输入流同步,其实现逻辑是编译器所定义的
– 输出流可以通过设置 unitbuf 来保证每次输出后自动同步

或

或

unitbuf 会立即刷新,但是会影响性能
补充:std::cout 和std::cerro有什么区别?
std::cout 和 std::cerr 是 C++ 标准库中用于输出的流对象,但它们在以下几个方面有所不同:
-
输出位置:
std::cout对应标准输出流(stdout),而std::cerr对应标准错误流(stderr)。这意味着通过std::cout输出的内容会显示在正常的输出中,而std::cerr输出的内容则被视为错误信息,并显示在错误流中。 -
缓冲机制:
std::cout使用标准输出缓冲区,而std::cerr使用非缓冲的错误流。这意味着std::cout输出的内容通常会在缓冲区满、换行符出现或程序正常终止时刷新并显示,而std::cerr的输出则会立即显示,无需等待缓冲区刷新。 -
用途:
std::cout主要用于一般的标准输出,例如打印程序的正常输出、结果等。而std::cerr则常用于报告错误、警告和异常情况,以便能够及时注意和处理程序中的问题。
由于 std::cerr 是一个非缓冲的流,并且将其输出视为错误信息,因此在编写调试代码和处理异常时,使用 std::cerr 可以获得更及时和准确的错误报告,以便于调试和追踪问题。而 std::cout 则适合用于一般的输出和日志记录。
需要注意的是,std::cout 和 std::cerr 都是流对象,可以像其他流一样使用流操作符(<<)来输出数据。例如:
#include <iostream>
int main() {
int value = 42;
std::cout << "Value: " << value << std::endl; // 正常输出
std::cerr << "Error: Something went wrong!" << std::endl; // 错误输出
return 0;
}
在使用时,根据你的需求选择适当的输出流对象。-end
● 基于绑定 (tie) 的同步
– 流可以绑定到一个输出流上,这样在每次输入 / 输出前可以刷新输出流的缓冲区
– 比如: cin 绑定到了 cout 上,所以输入之前的输出 都会打印出来
● 与 C 语言标准 IO 库的同步
– 缺省情况下, C++ 的输入输出操作会与 C 的输入输出函数同步
可以通过 sync_with_stdio 关闭该同步
本章大量参考了:cppreference 中流相关的内容。


















