文件操作:fstream库、文件读写、二进制文件、文件状态检查

文件操作这件事,说白了就是让程序跟硬盘上的数据打交道。我刚开始学C++那会儿,总觉得文件操作是个很麻烦的事——打开、读写、关闭,一步不对就崩给你看。后来做项目做多了才发现,其实核心就那么几个套路,掌握了就一通百通。

今天咱们就聊聊C++里最常用的文件操作方式:fstream库。嗯,这玩意儿是标准库的一部分,跨平台,用起来也顺手。

1. 文件流的三兄弟

C++把文件操作封装成了流对象,跟咱们用cin/cout的思路一模一样。主要有三个类:

类名 作用 默认打开模式
ifstream 输入文件流(读文件) ios::in
ofstream 输出文件流(写文件) ios::out
fstream 文件流(读写都行) ios::in | ios::out

我个人习惯是:只读用ifstream,只写用ofstream,又读又写才用fstream。这样代码意图清晰,不容易搞混。

2. 打开文件——别忘检查

打开文件其实就一行代码:

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

int main() {
    ifstream fin("data.txt");
    if (!fin.is_open()) {
        cerr << "文件打开失败!" << endl;
        return -1;
    }
    // 读文件...
    fin.close();
    return 0;
}
注意:打开文件后一定要检查是否成功。我在项目中遇到过好几次,文件名写错了或者路径不对,程序直接崩溃。后来养成了习惯——打开必检查,不检查不往下走。

打开模式可以组合使用,比如:

ofstream fout("log.txt", ios::app);  // 追加模式
fstream fio("data.bin", ios::in | ios::out | ios::binary);  // 二进制读写

3. 文本文件读写——最常用的方式

文本文件的读写,跟用cin/cout几乎一样。写文件:

ofstream fout("students.txt");
fout << "张三" << " " << 18 << endl;
fout << "李四" << " " << 20 << endl;
fout.close();

读文件:

ifstream fin("students.txt");
string name;
int age;
while (fin >> name >> age) {
    cout << name << " " << age << endl;
}
fin.close();
小技巧:while (fin >> ...)这种写法,读到文件末尾会自动退出循环。我曾经见过有人用while (!fin.eof()),结果多读了一行——因为eof标志是在读取失败后才设置的。

如果要读整行,用getline

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

4. 二进制文件——存啥都行

二进制文件的好处是:不经过任何格式转换,直接把内存数据原样写入。适合存结构体、数组、图片等数据。

写二进制:

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

Student stu = {"张三", 18, 95.5};
ofstream fout("student.bin", ios::binary);
fout.write(reinterpret_cast<const char*>(&stu), sizeof(stu));
fout.close();

读二进制:

Student stu;
ifstream fin("student.bin", ios::binary);
fin.read(reinterpret_cast<char*>(&stu), sizeof(stu));
fin.close();
核心要点:二进制读写必须用read()write()方法,参数是char*指针和字节数。记得用reinterpret_cast做类型转换。

你想想看,如果存一个包含100万个整数的数组,用文本方式写出来得占多少空间?每个数字转成字符串,还得加空格或换行。二进制直接写,4字节一个整数,干净利落。

5. 文件状态检查——别等出错了才后悔

文件操作中,状态检查是个容易被忽略但极其重要的环节。我早期写代码时经常跳过这一步,结果线上出bug查了半天,最后发现是文件没打开成功。

常用的状态检查方法:

方法 说明
is_open() 文件是否成功打开
good() 流状态是否正常(无错误、未到末尾)
fail() 上次操作是否失败(比如类型不匹配)
bad() 是否发生严重错误(比如磁盘损坏)
eof() 是否到达文件末尾

一个完整的读写示例:

ifstream fin("data.txt");
if (!fin.is_open()) {
    cerr << "无法打开文件" << endl;
    return;
}

int value;
while (fin >> value) {
    // 处理数据
    cout << value << " ";
}

if (fin.eof()) {
    cout << "\n文件读取完毕" << endl;
} else if (fin.fail()) {
    cerr << "\n读取过程中遇到格式错误" << endl;
}

fin.close();
避坑指南:我曾经在项目中用while (!fin.eof())来循环读取,结果最后一条数据被读了两次。原因是:eof()在读取尝试失败后才返回true,而不是在读到末尾时立即返回。正确的做法是用while (fin >> value)

6. 文件指针——随机访问

有时候我们需要跳到文件的某个位置去读写,比如修改某个记录。这时候就要用到文件指针:

fstream fio("data.bin", ios::in | ios::out | ios::binary);
// 跳到第3个Student的位置(假设每个Student占40字节)
fio.seekg(2 * sizeof(Student), ios::beg);
Student stu;
fio.read(reinterpret_cast<char*>(&stu), sizeof(stu));

// 修改后写回
stu.score = 100.0;
fio.seekp(2 * sizeof(Student), ios::beg);
fio.write(reinterpret_cast<const char*>(&stu), sizeof(stu));

fio.close();

常用的定位方法:

  • seekg(offset, origin) —— 设置读指针位置
  • seekp(offset, origin) —— 设置写指针位置
  • tellg() —— 获取当前读指针位置
  • tellp() —— 获取当前写指针位置

origin可以是ios::beg(文件开头)、ios::cur(当前位置)、ios::end(文件末尾)。

7. 知识体系总览

下面这张图把本章的核心内容串起来了,你可以对照着梳理一下思路:

文件操作 fstream 文本文件 二进制文件 文件状态检查 ifstream 读 ofstream 写 << >> getline read() write() reinterpret_cast is_open() good/fail/bad eof() 判断末尾 打开 → 检查状态 → 读写 → 关闭

嗯,这张图把整个文件操作的流程和分类都画清楚了。核心就是:打开文件、检查状态、读写数据、关闭文件。每一步都不能省。

8. 总结一下

文件操作其实没那么玄乎。记住几个关键点:

  • 文本文件<<>>,简单直观,适合人类阅读
  • 二进制文件read()write(),效率高,适合存结构化数据
  • 状态检查不能省,is_open()while (fin >> ...)是黄金搭档
  • 文件指针让你能随机访问,适合修改大文件中的某一部分

我在实际项目中,日志文件用文本格式,方便排查问题;配置文件用文本格式,方便修改;但像地图数据、模型参数这种,一律用二进制,又快又省空间。你根据自己的场景选就行。

最后一个小建议:写文件时记得调用close(),或者让文件流对象在作用域结束时自动析构。否则缓冲区里的数据可能没真正写到磁盘上——这个坑我踩过,数据丢了半天的活儿,心疼得很。

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