25、文件操作:文件流(fstream, ifstream, ofstream)、文本文件读写、二进制文件读写、文件定位

文件操作,说白了就是让程序能跟硬盘上的数据打交道。你写的代码跑在内存里,一断电就全没了。但文件不一样,它是持久化的。我刚开始学C++时,觉得文件操作就是「读进来、写出去」,没啥技术含量。直到有一次我写一个日志系统,因为没处理好二进制和文本模式的区别,导致跨平台时数据全乱了……嗯,从那以后我再也不敢小看文件操作了。

25.1 文件流的三兄弟:fstream、ifstream、ofstream

C++ 标准库提供了三个核心类来处理文件:

  • ifstream:输入文件流,专门用来读文件。
  • ofstream:输出文件流,专门用来写文件。
  • fstream:文件流,既能读也能写。

我个人习惯是:如果只需要读,就用 ifstream;只需要写,就用 ofstream;需要同时读写,才用 fstream。这样代码意图更清晰,别人一看就知道你要干嘛。

核心要点:文件流对象在生命周期结束时,会自动调用析构函数关闭文件。但为了保险,我建议你手动调用 .close(),尤其是在长时间运行的程序里。

25.2 文本文件读写

文本文件,就是你能用记事本打开、直接看到内容的文件。读写方式很简单,用 <<>> 操作符就行。

写文本文件

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

int main() {
    ofstream outFile("example.txt");
    if (!outFile.is_open()) {
        cerr << "文件打开失败!" << endl;
        return 1;
    }

    outFile << "姓名:张三" << endl;
    outFile << "年龄:25" << endl;
    outFile << "分数:92.5" << endl;

    outFile.close();
    return 0;
}

读文本文件

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

int main() {
    ifstream inFile("example.txt");
    if (!inFile.is_open()) {
        cerr << "文件打开失败!" << endl;
        return 1;
    }

    string line;
    while (getline(inFile, line)) {
        cout << line << endl;
    }

    inFile.close();
    return 0;
}

小技巧:getline() 读取整行,比用 >> 更安全。>> 遇到空格就停了,容易丢数据。我在项目中遇到过用 >> 读配置文件,结果字段里有空格,解析全乱套了。

25.3 二进制文件读写

二进制文件,说白了就是按字节读写,不关心内容是什么。图片、视频、可执行文件都是二进制格式。读写时用 read()write() 成员函数。

写二进制文件

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

struct Student {
    char name[32];
    int age;
    double score;
};

int main() {
    Student stu = {"李四", 22, 88.5};

    ofstream outFile("student.dat", ios::binary);
    if (!outFile.is_open()) {
        cerr << "文件打开失败!" << endl;
        return 1;
    }

    outFile.write(reinterpret_cast<char*>(&stu), sizeof(stu));
    outFile.close();
    return 0;
}

读二进制文件

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

struct Student {
    char name[32];
    int age;
    double score;
};

int main() {
    Student stu;

    ifstream inFile("student.dat", ios::binary);
    if (!inFile.is_open()) {
        cerr << "文件打开失败!" << endl;
        return 1;
    }

    inFile.read(reinterpret_cast<char*>(&stu), sizeof(stu));
    cout << "姓名:" << stu.name << endl;
    cout << "年龄:" << stu.age << endl;
    cout << "分数:" << stu.score << endl;

    inFile.close();
    return 0;
}

警告:二进制文件读写时,一定要用 ios::binary 模式打开。否则在 Windows 系统下,换行符 \n 会被自动转换成 \r\n,导致数据错位。我曾经因为这个 bug 排查了整整一个下午……

25.4 文件定位

文件定位,就是控制「当前读写位置」在文件中的哪里。C++ 提供了两个关键函数:

  • tellg() / tellp():获取当前读/写位置(返回 streampos 类型)。
  • seekg() / seekp():设置读/写位置。

你想想看,为什么要有定位?因为有时候你不想从头读到尾,比如你想直接跳到文件第100个字节开始读。这时候定位就派上用场了。

seekg 的三种定位方式

定位方式 说明 示例
ios::beg 从文件开头算起 seekg(10, ios::beg) → 跳到第10字节
ios::cur 从当前位置算起 seekg(5, ios::cur) → 向后跳5字节
ios::end 从文件末尾算起 seekg(-10, ios::end) → 跳到倒数第10字节

示例:读取文件最后20个字节

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

int main() {
    ifstream inFile("example.txt", ios::binary);
    if (!inFile.is_open()) {
        cerr << "文件打开失败!" << endl;
        return 1;
    }

    // 跳到文件末尾前20字节
    inFile.seekg(-20, ios::end);

    char buffer[21] = {0};
    inFile.read(buffer, 20);
    cout << "最后20字节:" << buffer << endl;

    inFile.close();
    return 0;
}

避坑指南:我曾经在读取大文件时,忘了检查 seekg() 是否成功。结果文件比预期小,定位到负数位置,读出来的数据全是乱码。所以记得用 if (!inFile) { /* 处理错误 */ } 来检查定位是否成功。

25.5 知识体系总览

下面这张图,帮你把本章的知识点串起来:

C++ 文件操作 文本文件 ifstream / ofstream << 和 >> 操作符 getline() 按行读取 二进制文件 ios::binary 模式 read() / write() reinterpret_cast<> 文件定位 tellg() / tellp() seekg() / seekp() beg / cur / end

这张图把文件操作分成了三大块:文本文件、二进制文件、文件定位。每一块都有对应的类和函数。你写代码时,先想清楚你要处理什么类型的文件,再选对应的工具。别一上来就 fstream 一把梭,那样容易出问题。

总结一下:文件操作是C++里非常实用的技能。文本文件适合人类可读的数据,二进制文件适合程序内部数据。文件定位让你能随机访问文件内容,而不是只能从头读到尾。记住:打开文件后一定要检查是否成功,读写完毕后记得关闭文件。这些习惯能帮你省下不少调试时间。

公众号:蓝海资料掘金营,微信deep3321