C++ 文件 IO

文件 IO(input/output)是程序和外部数据交互的基本方式——读配置、写日志、处理数据文件。C++ 提供 ifstream(读)、ofstream(写)、fstream(读写)三个类来操作文件,它们和 cin/cout 用法几乎一样(都用 >> << 运算符)。这一章我们看 C++ 文件 IO 的全部要点。

一、C++ 文件 IO 的层次

C++ 提供两套文件 IO:

高层 IO 全部基于 RAII——构造时打开,析构时自动关闭,你几乎不用手动调用 close。

二、写文件:ofstream

ofstream(output file stream)用于写文件:

#include <iostream>
#include <fstream>    // ifstream/ofstream 都在这里
using namespace std;

int main() {
    // ofstream:output file stream,用于写文件
    // 构造时打开文件(RAII),析构时自动关闭
    ofstream out("hello.txt");

    if (!out) {
        cerr << "无法打开文件!" << endl;
        return 1;
    }

    // 用 << 运算符写,和 cout 完全一样
    out << "Hello, File IO!" << endl;
    out << "这是第二行" << endl;
    out << "数字: " << 42 << ", 浮点: " << 3.14 << endl;

    // 出作用域,out 析构自动关闭文件
    // 也可以手动 close:out.close();
    return 0;
}

要点:

三、读文件:ifstream

ifstream(input file stream)用于读文件:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // ifstream:input file stream,用于读文件
    ifstream in("hello.txt");

    if (!in) {
        cerr << "无法打开文件!" << endl;
        return 1;
    }

    // 1. 逐词读取(以空格/换行分隔)
    string word;
    while (in >> word) {
        cout << "词: " << word << endl;
    }

    in.clear();    // 清除 EOF 标志,才能继续读
    in.seekg(0);   // 把读指针移回文件开头

    // 2. 逐行读取(保留行内空格)
    string line;
    while (getline(in, line)) {
        cout << "行: " << line << endl;
    }

    return 0;
}

两种读取方式对比:

四、文件打开模式

open 时可以指定模式,用 | 组合:

#include <fstream>
using namespace std;

int main() {
    // 文件打开模式(可用 | 组合):
    // ios::in       读
    // ios::out      写(默认截断)
    // ios::app      append,在末尾追加
    // ios::ate      打开后定位到末尾
    // ios::trunc    截断(覆盖已有内容)
    // ios::binary   二进制模式(不做换行转换)

    // 默认:ofstream 用 out|trunc,ifstream 用 in

    ofstream o1("a.txt");                 // 写,覆盖
    ofstream o2("b.txt", ios::app);       // 写,追加
    ofstream o3("c.txt", ios::out | ios::binary);  // 二进制写

    ifstream i1("a.txt");                 // 读
    ifstream i2("data.bin", ios::in | ios::binary);  // 二进制读

    // fstream:可读可写
    fstream fs("d.txt", ios::in | ios::out | ios::app);
    return 0;
}

常见模式:

五、读取整个文件

有时你想一次性读完整个文件:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    ifstream in("hello.txt");
    if (!in) return 1;

    // 方法 1:用 stringstream 一次性读完整个文件
    stringstream ss;
    ss << in.rdbuf();            // 把整个文件读进 stringstream
    string content = ss.str();
    cout << "全文(" << content.size() << " 字节):" << endl;
    cout << content << endl;

    // 方法 2:C++ 的 string 流读取技巧
    in.clear();
    in.seekg(0, ios::end);
    size_t size = in.tellg();    // 获取文件大小
    in.seekg(0, ios::beg);

    string buf(size, '\0');
    in.read(&buf[0], size);      // 一次性读到 buffer
    cout << "读到 " << buf.size() << " 字节" << endl;

    return 0;
}

六、二进制文件

二进制 IO 用于高效存储结构化数据(图片、视频、序列化的 struct):

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

struct Point {
    int x, y;
};

int main() {
    // 写二进制文件
    {
        ofstream out("points.bin", ios::binary);
        vector<Point> pts = {{1, 2}, {3, 4}, {5, 6}};
        // write 需要const char* + 字节数
        out.write(reinterpret_cast<const char*>(pts.data()),
                  pts.size() * sizeof(Point));
        // out 析构自动关闭
    }

    // 读二进制文件
    {
        ifstream in("points.bin", ios::binary);
        vector<Point> pts(3);
        in.read(reinterpret_cast<char*>(pts.data()),
                3 * sizeof(Point));

        for (const auto& p : pts) {
            cout << "(" << p.x << ", " << p.y << ")" << endl;
        }
    }
    return 0;
}
// 注意:
// 1. reinterpret_cast 在二进制 IO 中常见(把对象转成字节流)
// 2. 二进制格式跨平台不兼容(字节序、对齐、类型大小)
// 3. 含指针/STL 容器的类不能直接二进制读写
// 4. 需要跨平台用 JSON/MessagePack/Protobuf 等序列化

二进制 IO 的要点:

七、stringstream:字符串流

stringstream 把"字符串"当"文件"操作,非常有用:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    // stringstream:字符串流,把字符串当文件操作
    // 用途:类型转换、字符串拼接、解析

    // 1. 数字转字符串
    stringstream ss1;
    ss1 << "age=" << 25 << ", pi=" << 3.14;
    string s1 = ss1.str();
    cout << s1 << endl;             // age=25, pi=3.14

    // 2. 字符串转数字
    stringstream ss2("42 3.14 hello");
    int n;
    double d;
    string w;
    ss2 >> n >> d >> w;
    cout << n << " " << d << " " << w << endl;  // 42 3.14 hello

    // 3. 解析 CSV/日志
    string logLine = "2024-01-01,INFO,Server started";
    stringstream ss3(logLine);
    string date, level, msg;
    getline(ss3, date, ',');    // 用逗号分隔
    getline(ss3, level, ',');
    getline(ss3, msg);
    cout << "[" << date << "][" << level << "] " << msg << endl;

    // C++11 还可以直接用 to_string / stoi / stod
    // 但 stringstream 更灵活,适合复杂场景
    return 0;
}

stringstream 的典型用途:

八、文件位置指针(seek)

读写文件时,有个"当前位置指针"。可以手动控制:

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    fstream fs("data.txt", ios::in | ios::out | ios::trunc);
    if (!fs) return 1;

    // 写入一些数据
    fs << "ABCDEFGHIJ";

    // 文件位置指针(读写位置)
    fs.seekp(3);              // 把"写指针"移到位置 3(D)
    fs << "XX";               // 把 DE 改成 XX,现在文件是 ABCXXFGHIJ

    fs.seekg(0);              // 把"读指针"移回开头
    string content;
    fs >> content;
    cout << content << endl;  // ABCXXFGHIJ

    fs.seekg(0, ios::end);
    streampos size = fs.tellg();   // 文件大小
    cout << "文件大小: " << size << " 字节" << endl;
    return 0;
}

// 两个指针:
// - 读指针(seekg/tellg):控制读位置
// - 写指针(seekp/tellp):控制写位置
// fstream 共享一个位置,但 ifstream 只有 g,ofstream 只有 p

// 三个定位参考点:
// ios::beg   文件开头
// ios::cur   当前位置
// ios::end   文件末尾

九、错误处理

文件 IO 容易出错(文件不存在、权限不足、磁盘满)。正确处理:

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream in("nonexistent.txt");

    // 检查打开是否成功
    if (!in) {
        cerr << "打开失败" << endl;
        // 错误状态位:
        // - good():一切正常
        // - eof():  到达文件末尾(EOF)
        // - fail(): 操作失败(如类型不匹配),可恢复
        // - bad():  严重错误(如磁盘坏),不可恢复
    }

    // 流的状态可以用 bool 测试
    while (in >> x) {  // 读到 EOF 后,!in 为 true,循环结束
    }

    // 清除错误状态
    in.clear();

    // 推荐用异常模式处理 IO 错误(可选)
    ifstream file("data.txt");
    file.exceptions(ifstream::failbit | ifstream::badbit);
    // 现在打开失败、读失败都会抛异常
    try {
        int x;
        file >> x;
    } catch (const ios_base::failure& e) {
        cerr << "IO 异常: " << e.what() << endl;
    }
    return 0;
}

几个状态判断函数:

异常模式(file.exceptions(...))让你不用每次手动检查,出错自动抛异常。但默认不开启。

十、实战技巧

小结

这一章你掌握了 C++ 文件 IO:ofstream 写文件、ifstream 读文件、fstream 读写、打开模式、按行/按词/一次性读、二进制 IO、stringstream 字符串流、seek 文件定位、错误状态判断。恭喜,你已经完成了 C++ 入门系列的全部内容!

系列结语

恭喜你读完了 C++ 入门 16 篇!回顾一下你的旅程:

下一步学什么?

编程是手艺活,继续边读边敲。Good luck!

← 上一篇 C++ 异常

← 返回 C++ 教程目录

✈️💬