11. 文件输入输出:文件流与文件操作
文件操作,说白了就是让程序能读写硬盘上的数据。我刚开始学C++时,觉得这功能没什么大不了。直到有一次,我写了一个学生成绩管理系统,每次关闭程序数据就丢了……嗯,从那以后,我才真正重视起文件操作。
这一章,我们来聊聊C++里怎么跟文件打交道。我会把文件流、文本文件、二进制文件这些概念,用我自己的经验串起来讲。
11.1 文件流的三兄弟:fstream、ifstream、ofstream
C++把文件看作一种流。就像cin和cout是标准输入输出流一样,文件也有自己的流对象。它们都定义在
这三兄弟的分工很明确:
- ifstream:专门用来读文件(input file stream)
- ofstream:专门用来写文件(output file stream)
- fstream:既能读又能写,是前两个的合体
打开文件的方式很简单:
#include <fstream>
#include <iostream>
using namespace std;
int main() {
// 方式一:构造时直接打开
ofstream outFile("data.txt");
// 方式二:先构造,再手动打开
ifstream inFile;
inFile.open("data.txt");
// 方式三:用fstream,指定模式
fstream ioFile;
ioFile.open("data.txt", ios::in | ios::out);
return 0;
}
打开模式有很多种,我整理了一张表:
| 模式常量 | 含义 | 说明 |
|---|---|---|
| ios::in | 读模式 | 打开文件用于读取 |
| ios::out | 写模式 | 打开文件用于写入(会清空原有内容) |
| ios::app | 追加模式 | 写入时追加到文件末尾,不清空 |
| ios::binary | 二进制模式 | 以二进制方式读写 |
| ios::ate | 初始位置在末尾 | 打开后文件指针在末尾,但可以移动 |
| ios::trunc | 截断文件 | 如果文件存在,清空内容 |
11.2 文本文件读写
文本文件,就是你能用记事本打开、看到文字的那种。读写方式跟cin/cout几乎一样。
写文本文件:
#include <fstream>
using namespace std;
int main() {
ofstream fout("students.txt");
if (!fout) {
cerr << "文件打开失败!" << endl;
return 1;
}
fout << "张三" << " " << 85 << endl;
fout << "李四" << " " << 92 << endl;
fout << "王五" << " " << 78 << endl;
fout.close();
return 0;
}
读文本文件:
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main() {
ifstream fin("students.txt");
if (!fin) {
cerr << "文件打开失败!" << endl;
return 1;
}
string name;
int score;
while (fin >> name >> score) {
cout << name << "的成绩是:" << score << endl;
}
fin.close();
return 0;
}
读取整行的写法:
string line;
while (getline(fin, line)) {
cout << line << endl;
}
11.3 二进制文件读写
二进制文件,存的是数据的原始字节。比如一个int是4个字节,二进制文件就直接存这4个字节,而不是存成"42"这样的字符串。
为什么要用二进制?我举个例子:你要存一个double数组,100万个元素。用文本文件存,每个数字可能占10-20个字符,文件很大。用二进制存,每个double固定8个字节,文件小,读写也快。
写二进制文件:
#include <fstream>
using namespace std;
struct Student {
char name[20];
int age;
double score;
};
int main() {
Student stu = {"张三", 20, 88.5};
ofstream fout("student.dat", ios::binary);
if (!fout) {
cerr << "文件打开失败!" << endl;
return 1;
}
// 用 write 函数写入二进制数据
fout.write(reinterpret_cast<const char*>(&stu), sizeof(stu));
fout.close();
return 0;
}
读二进制文件:
#include <fstream>
#include <iostream>
using namespace std;
struct Student {
char name[20];
int age;
double score;
};
int main() {
Student stu;
ifstream fin("student.dat", ios::binary);
if (!fin) {
cerr << "文件打开失败!" << endl;
return 1;
}
// 用 read 函数读取二进制数据
fin.read(reinterpret_cast<char*>(&stu), sizeof(stu));
cout << "姓名:" << stu.name << endl;
cout << "年龄:" << stu.age << endl;
cout << "成绩:" << stu.score << endl;
fin.close();
return 0;
}
11.4 文件状态检查
文件操作经常会出错。比如文件不存在、磁盘满了、权限不够。我建议每次打开文件后,都检查一下状态。
常用的检查方法:
ifstream fin("data.txt");
// 方法一:直接判断对象
if (!fin) {
cerr << "文件打开失败" << endl;
}
// 方法二:用 is_open()
if (!fin.is_open()) {
cerr << "文件未成功打开" << endl;
}
// 方法三:检查流状态
if (fin.fail()) {
cerr << "操作失败" << endl;
}
if (fin.eof()) {
cout << "已读到文件末尾" << endl;
}
if (fin.bad()) {
cerr << "发生严重错误" << endl;
}
流状态位有四个:
| 状态位 | 含义 | 触发条件 |
|---|---|---|
| goodbit | 一切正常 | 没有错误 |
| eofbit | 到达文件末尾 | 读取到文件结束 |
| failbit | 操作失败 | 类型不匹配、文件不存在等 |
| badbit | 严重错误 | 底层流损坏 |
11.5 随机文件访问
文件就像一条磁带,默认是顺序读写的。但有时候,你想跳到文件的某个位置去读写。比如一个文件存了1000条记录,你想直接读第500条,不需要从头读到第499条。
这就是随机访问。C++提供了两个关键函数:
- seekg():设置读指针的位置(get pointer)
- seekp():设置写指针的位置(put pointer)
- tellg():获取当前读指针的位置
- tellp():获取当前写指针的位置
#include <fstream>
#include <iostream>
using namespace std;
int main() {
fstream file("data.bin", ios::in | ios::out | ios::binary);
if (!file) {
cerr << "文件打开失败" << endl;
return 1;
}
// 跳到第5个位置(从0开始计数)
file.seekg(5, ios::beg);
char ch;
file.get(ch);
cout << "第5个字符是:" << ch << endl;
// 获取当前位置
streampos pos = file.tellg();
cout << "当前位置:" << pos << endl;
// 从当前位置往后跳3个字节
file.seekg(3, ios::cur);
// 从文件末尾往前跳2个字节
file.seekg(-2, ios::end);
file.close();
return 0;
}
seekg的第二个参数有三个选项:
| 偏移基准 | 含义 |
|---|---|
| ios::beg | 从文件开头算起 |
| ios::cur | 从当前位置算起 |
| ios::end | 从文件末尾算起 |
11.6 知识体系总览
下面这张图,把本章的核心知识点串在了一起。你可以把它当作一个快速索引:
这张图展示了文件操作的核心流程。你从文件流出发,根据需求选择ifstream、ofstream或fstream。然后决定用文本方式还是二进制方式。如果需要跳转,就用随机访问。最后,别忘了检查文件状态,确保每一步都走对了。
文件操作其实不难,但细节很多。我建议你多写几个小例子练练手。比如写一个程序,把自己的名字和年龄存到文件里,再读出来。等你能熟练操作了,再尝试更复杂的场景。
公众号:蓝海资料掘金营,微信deep3321