文件操作: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. 知识体系总览
下面这张图把本章的核心内容串起来了,你可以对照着梳理一下思路:
嗯,这张图把整个文件操作的流程和分类都画清楚了。核心就是:打开文件、检查状态、读写数据、关闭文件。每一步都不能省。
8. 总结一下
文件操作其实没那么玄乎。记住几个关键点:
- 文本文件用
<<和>>,简单直观,适合人类阅读 - 二进制文件用
read()和write(),效率高,适合存结构化数据 - 状态检查不能省,
is_open()和while (fin >> ...)是黄金搭档 - 文件指针让你能随机访问,适合修改大文件中的某一部分
我在实际项目中,日志文件用文本格式,方便排查问题;配置文件用文本格式,方便修改;但像地图数据、模型参数这种,一律用二进制,又快又省空间。你根据自己的场景选就行。
close(),或者让文件流对象在作用域结束时自动析构。否则缓冲区里的数据可能没真正写到磁盘上——这个坑我踩过,数据丢了半天的活儿,心疼得很。
公众号:蓝海资料掘金营,微信deep3321