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 中流相关的内容。