文件与流
原理
流的缓冲区三层架构
C++ IO 流采用三层架构,将用户程序的格式化和底层系统调用解耦:
用户程序层: ofstream / ifstream / cout ← 格式化 >> / << / getline
缓冲区层: streambuf / filebuf ← 内部字符数组,减少系统调用
系统调用层: read() / write() (POSIX) ← 用户态→内核态切换
```cpp
### 输出流程
`ofstream << "hello"` 的数据流经路径:
1. 数据写入 streambuf 的内部缓冲区
2. 缓冲区满(或显式 `flush`、遇到 `endl`、程序正常结束)时
3. 通过 `write()` 系统调用一次性写入内核
4. 内核将数据写入磁盘(可能通过页缓存进一步延迟)
### 读取流程
`ifstream >> value` 的数据流经路径:
1. 内核通过 `read()` 从磁盘读取一大块数据(如 4KB)
2. 放入 streambuf 的缓冲区
3. 用户程序从缓冲区逐字节/逐行取数据
4. 缓冲区耗尽时再次触发 `read()`
### 三种缓冲策略
| 策略 | 触发条件 | 典型流 |
|---|---|---|
| 全缓冲 | 缓冲区满才执行系统调用 | `ofstream`(默认 4KB 或更大) |
| 行缓冲 | 遇到 `\n` 时刷新 | `cout`(连接终端时) |
| 无缓冲 | 每次写操作立即系统调用 | `cerr`(保证错误立即输出) |
> `endl` = `\n` + `flush`。在不需要立即刷新缓冲区的场景(性能敏感),应使用 `\n` 而非 `endl`。
### 文件描述符与内核文件表
每个 C++ fstream 对象内部关联一个文件描述符(fd)。fd 是进程文件描述符表中的索引,指向内核级"打开文件表"中的一项。该表项记录当前文件偏移量、打开模式和 inode 指针。
```cpp
进程 fd 表 内核打开文件表 inode
┌──────┐ ┌──────────────┐ ┌────────┐
│ 0: STDIN │ │ offset: 1024 │ │ size │
│ 1: STDOUT│ │ mode: RDONLY │───→│ blocks │
│ 3: fd ───┼─────→│ inode ptr │ └────────┘
└──────┘ └──────────────┘
```cpp
### sync_with_stdio
默认情况下 C++ 流与 C 标准库的 `stdin`/`stdout`/`stderr` 保持同步(`ios_base::sync_with_stdio(true)`),允许混合使用 `cout` 和 `printf`。同步的代价是每次 IO 操作要刷新双边缓冲区。如果程序只用 C++ 流,可关闭同步以获性能提升:
```cpp
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
```cpp
### 二进制模式的实质
二进制模式(`ios::binary`)在 Linux 上与文本模式无区别——因为 Linux 使用 `\n` 换行。在 Windows 上,文本模式会自动将 `\n` 转换为 `\r\n`,二进制模式不做此转换。二进制读写不进行任何字符转换,直接操作字节。
---
## 语法
### 文件流
```cpp
std::ofstream out("file.txt"); // 写(默认 ios::out | ios::trunc)
std::ofstream app("file.txt", std::ios::app); // 追加
std::ifstream in("file.txt"); // 读(默认 ios::in)
std::fstream io("file.txt", std::ios::in | std::ios::out); // 读写
if (!in.is_open()) { /* 打开失败 */ }
```cpp
### 流状态
```cpp
stream.good(); // 无错误
stream.eof(); // 到达文件尾
stream.fail(); // 操作失败(如类型转换失败)
stream.bad(); // 不可恢复错误
stream.clear(); // 重置所有状态
```cpp
### 格式化输出(iomanip)
```cpp
std::cout << std::setw(15) << std::left << "Name"
<< std::setprecision(3) << 3.14159
<< std::fixed << 123.456
<< std::hex << 255 // 0xff
<< std::boolalpha << true; // "true"
```asm
### 二进制读写
```cpp
file.write(reinterpret_cast<const char*>(&obj), sizeof(obj));
file.read(reinterpret_cast<char*>(&obj), sizeof(obj));
// seekg(offset, beg/cur/end) — 移动读指针
// seekp(offset, beg/cur/end) — 移动写指针
// tellg() / tellp() — 获取当前指针位置
```cpp
### 字符串流
```cpp
std::ostringstream oss;
oss << "Name: " << name << ", Age: " << age;
std::string result = oss.str();
std::istringstream iss("42 3.14 hello");
iss >> i >> d >> s; // 从字符串中解析
```cpp
### C++17 filesystem
```cpp
namespace fs = std::filesystem;
fs::exists(path); // 检查存在
fs::create_directory(dir); // 创建目录
fs::create_directories("a/b/c"); // 创建多级目录
for (auto& entry : fs::directory_iterator(dir)) { /* ... */ }
for (auto& entry : fs::recursive_directory_iterator(dir)) { /* ... */ }
auto size = fs::file_size(path);
fs::remove_all(dir); // 递归删除
```cpp
---
## 实践
**练习**:文件处理题(文件读入与字符串处理),格式化输出题(格式化输出)。