前置声明
#include <fstream>
#include <iostream>
#include <string>
#include <limits> // for numeric_limits
using namespace std;
文件操作
C++ 提供 <fstream> 库进行文件读写,主要类:
ifstream:输入文件流(读)ofstream:输出文件流(写)fstream:双向文件流(读写)
打开文件
ifstream fin; // 仅读
fin.open("data.txt"); // 默认以 in 模式打开
ofstream fout; // 仅写
fout.open("output.txt"); // 默认以 out | trunc 模式打开
fstream fs;
fs.open("both.txt", ios::in | ios::out); // 读写模式
打开模式(可用 | 组合):
| 模式标志 | 含义 |
|---|---|
ios::in | 读 |
ios::out | 写(若文件存在则清空) |
ios::app | 追加写 – 每次写入前自动定位到文件尾 |
ios::ate | 打开后定位到文件尾 |
ios::trunc | 清空文件 |
ios::binary | 二进制模式 |
使用构造函数直接打开:
ifstream fin("data.txt"); // 自动调用 open
ofstream fout("output.txt");
检查文件是否打开成功
if (!fin.is_open()) {
cerr << "无法打开文件" << endl;
return 1;
}
// 或者直接使用 if (fin)
if (!fin) {
cerr << "打开失败" << endl;
}
关闭文件
fin.close(); // 析构时会自动关闭,但显式关闭可提前释放资源
读写示例
读取文本文件
ifstream fin("example.txt");
if (!fin) {
cerr << "打开文件失败\n";
return 1;
}
string line;
while (getline(fin, line)) { // 逐行读取
cout << line << endl;
}
// 也可以使用 >> 读取单词(跳过空白)
// string word; while (fin >> word) { ... }
fin.close();
普通写入(覆盖模式)
ofstream fout("output.txt");
if (fout) {
fout << "Hello, world!" << endl;
fout << "Line 2: " << 123 << endl;
}
追加写入(保留原有内容)
使用 ios::app 模式,每次写入都会自动追加到文件末尾。
ofstream fout("log.txt", ios::app); // 追加模式
if (fout) {
fout << "新的日志行" << endl;
fout << "追加时间: " << time(nullptr) << endl;
}
// 也可以先 open,再用 app 模式
ofstream fout2;
fout2.open("log.txt", ios::app);
对于 fstream 同时读写并追加:
fstream fs("data.txt", ios::in | ios::out | ios::app);
// 此时读可以从头开始,写总是在末尾追加
fs << "追加的内容" << endl;
⚠️ 注意:追加模式下,即便使用 seekp() 也无法改变写入位置,每次 write 或 << 都会自动移到文件尾。
错误状态与清理
fin.clear(); // 清除错误标志
fin.seekg(0, ios::beg); // 重置读取位置
fin.ignore(numeric_limits<streamsize>::max(), '\n'); // 跳过当前行
二进制文件读写(略,不重要)
必须使用 ios::binary 模式。读写使用 read() / write()。
#include <fstream>
#include <cstring>
struct Record {
int id;
double value;
char name[32];
};
// 写入二进制
ofstream fout("data.bin", ios::binary);
Record r1{1, 3.14, "example"};
fout.write(reinterpret_cast<const char*>(&r1), sizeof(Record));
// 读取二进制
ifstream fin("data.bin", ios::binary);
Record r2;
fin.read(reinterpret_cast<char*>(&r2), sizeof(Record));
⚠️ 注意:对象中包含指针(如 string)时不能直接整体读写,应序列化各成员。
文件指针移动(随机访问)(略,不重要)
| 操作 | ifstream | ofstream | fstream |
|---|---|---|---|
| 获取读位置 | tellg() | – | tellg() |
| 设置读位置 | seekg() | – | seekg() |
| 获取写位置 | – | tellp() | tellp() |
| 设置写位置 | – | seekp() | seekp() |
fstream fs("data.txt", ios::in | ios::out);
fs.seekg(10, ios::beg); // 读位置移动到第10字节
fs.seekp(0, ios::end); // 写位置移动到文件尾
auto pos = fs.tellg(); // 获取当前位置(类型 streampos)
fs.seekg(pos); // 恢复位置
偏移方向:ios::beg(开头)、ios::cur(当前)、ios::end(结尾)。
文件重定向
将标准输入(cin)、标准输出(cout)或标准错误(cerr)重定向到文件,或反之。
C 风格:freopen
最简单的方法,但可能与其他流交互时不够安全。
#include <cstdio>
// 将标准输入重定向到 input.txt
freopen("input.txt", "r", stdin);
// 将标准输出重定向到 output.txt
freopen("output.txt", "w", stdout);
// 恢复(通常不需要显式恢复,程序结束即还原)
重定向后 scanf/printf 或 cin/cout 都会受影响:
#include <iostream>
#include <cstdio>
int main() {
freopen("in.txt", "r", stdin);
int a, b;
cin >> a >> b; // 从 in.txt 读取
freopen("out.txt", "w", stdout);
cout << a + b << endl; // 输出到 out.txt
return 0;
}
⚠️ 注意:freopen 后 cin/cout 仍可用,但文件描述符被替换。某些平台下关闭原始控制台流可能产生副作用。
C++ 风格:rdbuf() 重定向流缓冲区
更安全、可控的方法,通过替换流的内部 streambuf 实现。
#include <fstream>
#include <iostream>
int main() {
ifstream fin("input.txt");
ofstream fout("output.txt");
if (!fin || !fout) {
cerr << "打开文件失败\n";
return 1;
}
// 保存原始的 cin / cout 缓冲区
auto* cin_buf = cin.rdbuf();
auto* cout_buf = cout.rdbuf();
// 重定向 cin 从文件读取
cin.rdbuf(fin.rdbuf());
// 重定向 cout 写入文件
cout.rdbuf(fout.rdbuf());
// 现在 cin 将从 input.txt 读取,cout 输出到 output.txt
string s;
cin >> s; // 从文件读
cout << s; // 写入文件
// 恢复原始流缓冲区
cin.rdbuf(cin_buf);
cout.rdbuf(cout_buf);
// 文件流会随析构自动关闭
return 0;
}
优点:可临时重定向,之后恢复,不影响其他流。
重定向到 stringstream(略,不重要)
可用于捕获输出或伪造输入:
#include <sstream>
#include <iostream>
int main() {
stringstream fakeInput;
fakeInput << "42\nhello\n";
auto* oldCin = cin.rdbuf();
cin.rdbuf(fakeInput.rdbuf());
int x;
string y;
cin >> x >> y; // 从 fakeInput 读取
cout << x << ", " << y << endl; // 输出 42, hello
cin.rdbuf(oldCin);
return 0;
}
追加总结
| 方式 | 代码示例 | 说明 |
|---|---|---|
ofstream 追加 | ofstream fout("file.txt", ios::app); | 最常用,每次写入自动到末尾 |
ofstream 先打开后设模式 | fout.open("file.txt", ios::app); | 同上 |
fstream 读写追加 | fstream fs("file.txt", ios::in | ios::out | ios::app); | 可读可写,写总是在末尾 |
freopen 追加到 stderr/stdout | freopen("log.txt", "a", stderr); | C风格重定向,追加模式 |
注意:追加模式下无法修改已存在的内容,如需在文件中间插入或修改,可使用普通读写模式(ios::out 或 ios::in|ios::out)配合 seekp(),但要注意覆盖而非插入。