文件操作与文件重定向

前置声明

#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)时不能直接整体读写,应序列化各成员。


文件指针移动(随机访问)(略,不重要)

操作ifstreamofstreamfstream
获取读位置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/printfcin/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;
}

⚠️ 注意:freopencin/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/stdoutfreopen("log.txt", "a", stderr);C风格重定向,追加模式

注意:追加模式下无法修改已存在的内容,如需在文件中间插入或修改,可使用普通读写模式(ios::outios::in|ios::out)配合 seekp(),但要注意覆盖而非插入。