一、文件操作基础概念
1.1 核心头文件
C++ 中进行文件操作,必须包含头文件:
#include <fstream>
1.2 文件的两种类型
根据存储格式,文件分为两大类:
1.3 三大文件流类
<fstream> 中提供了三个核心的流类,分别对应不同的操作场景:
表格
| ofstream | 写文件操作(输出流) | 内存 → 文件 |
| ifstream | 读文件操作(输入流) | 文件 → 内存 |
| fstream | 读写文件操作(输入输出流) | 双向读写 |
二、文本文件的读写
2.1 文本文件写操作
写文件完整步骤
文件打开方式
打开方式通过 ios 命名空间下的标志位控制,多个方式可以用按位或 | 组合使用:
表格
| ios::in | 以读取模式打开文件(读文件时使用) |
| ios::out | 以写入模式打开文件(写文件时使用) |
| ios::ate | 打开文件后,初始光标定位在文件末尾 |
| ios::app | 以追加模式写文件,新内容追加在文件尾部 |
| ios::trunc | 如果文件已存在,先删除原文件,再创建新文件 |
| ios::binary | 以二进制模式打开文件 |
💡 补充:如果不指定文件路径,默认在项目的相对路径下创建 / 查找文件。
写文件代码示例
#include <fstream>
#include <iostream>
using namespace std;
// 文本文件写操作示例
void test_write() {
// 1. 创建输出流对象
ofstream ofs;
// 2. 打开文件:路径 + 打开方式
ofs.open("test.txt", ios::out);
// 3. 写入数据,用法和cout一致
ofs << "姓名:张三" << endl;
ofs << "年龄:18" << endl;
ofs << "职业:程序员" << endl;
// 4. 关闭文件
ofs.close();
}
int main() {
test_write();
return 0;
}
关于「不手动关闭文件」的说明
很多人会听说 “栈上创建的文件对象可以不手动 close”,这里统一说明:
- C++ 中,栈上的文件流对象在生命周期结束时,会通过RAII 机制自动调用析构函数,关闭文件、释放资源。
- 但强烈建议手动调用 close ():
- 资源泄漏风险:系统能打开的文件句柄数量有限,长期运行的程序频繁打开不关闭,会耗尽句柄资源。
- 数据丢失风险:数据会先写入缓冲区,若程序异常退出,缓冲区数据可能没来得及刷入磁盘,造成数据丢失。
2.2 文本文件读操作
读文件完整步骤
判断文件打开成功
通过 is_open() 成员函数判断:返回 true 表示打开成功,返回 false 表示打开失败(比如文件不存在、路径错误、权限不足)。
四种读取方式详解
方式 1:>> 运算符(按单词读取)
利用流提取运算符 >> 读取,以空格、换行、制表符为分隔,每次读取一个 “单词”。
char buf[1024] = {0}; // 字符数组缓冲区
while (ifs >> buf) { // 读到文件尾时,返回false,循环结束
cout << buf << endl;
}
也可以用 string 接收,需要额外包含 #include <string>。
方式 2:成员函数 getline(按行读取,字符数组版)
调用流对象的 getline 成员函数,一次读取一整行。
char buf[1024] = {0};
// 参数1:缓冲区地址;参数2:最大读取字符数
while (ifs.getline(buf, sizeof(buf))) {
cout << buf << endl;
}
方式 3:全局 getline 函数(按行读取,string 版)
这是开发中最推荐的文本读取方式,无需手动管理缓冲区大小,使用更灵活。
#include <string> // 必须包含string头文件
string buf;
while (getline(ifs, buf)) {
cout << buf << endl;
}
方式 4:get () 逐字符读取
一次读取一个字符,直到读到文件结束符 EOF(End Of File)。
char c;
// ifs.get() 每次返回一个字符,读到EOF时结束
while ((c = ifs.get()) != EOF) {
cout << c;
}
特点:可以读取所有字符(包括空格、换行),但效率较低,适合特殊场景使用。
读文件完整代码示例
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
void test_read() {
ifstream ifs;
ifs.open("test.txt", ios::in);
// 必须判断打开是否成功
if (!ifs.is_open()) {
cout << "文件打开失败!请检查路径或文件是否存在" << endl;
return;
}
// 推荐用法:按行读取,string版本
string buf;
while (getline(ifs, buf)) {
cout << buf << endl;
}
ifs.close();
}
int main() {
test_read();
return 0;
}
三、二进制文件的读写
二进制方式直接操作内存的原始字节数据,不会做 ASCII 编码转换,读写效率更高、体积更小,适合存储结构体、类对象、数值数组等数据。
⚠️ 注意:二进制操作必须在打开方式中加上 ios::binary,否则会按文本解析,出现乱码。
3.1 二进制写文件
二进制写文件使用流对象的 write 成员函数。
- 函数原型:ostream& write(const char* buffer, int len);
- 参数说明:
- buffer:指向内存中待写入数据的指针
- len:要写入的字节数
代码示例(写入自定义类对象)
#include <fstream>
#include <iostream>
using namespace std;
// 自定义类:二进制持久化建议用char数组,不要用string
class Person {
public:
char m_Name[64]; // 姓名:固定大小字符数组
int m_Age; // 年龄
};
void test_binary_write() {
// 创建流对象+打开文件,也可以分开写
ofstream ofs("person.dat", ios::out | ios::binary);
Person p = {"张三", 18};
// 核心:将对象地址强转为 const char*,写入sizeof(Person)个字节
ofs.write((const char*)&p, sizeof(Person));
ofs.close();
}
❗ 重要坑点:二进制读写自定义类型时,成员变量不要使用 string。 string 的字符串数据存在堆内存中,对象里只存了指针,写入文件只是保存了一个临时地址;下次读取时这个地址已经无效,会造成非法访问和乱码。固定大小的 char 数组是二进制持久化的最佳选择。
3.2 二进制读文件
二进制读文件使用流对象的 read 成员函数。
- 函数原型:istream& read(char* buffer, int len);
- 参数说明:
- buffer:指向内存中接收数据的缓冲区
- len:要读取的字节数
代码示例(读取自定义类对象)
void test_binary_read() {
ifstream ifs;
ifs.open("person.dat", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
Person p;
// 核心:将对象地址强转为 char*,读取sizeof(Person)个字节
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
ifs.close();
}
四、常见问题与注意事项总结
- 需要人可读、跨平台文本内容 → 选文本文件
- 存储结构体、大量数值、追求性能 → 选二进制文件
五、全文总结
C++ 文件操作的核心是 <fstream> 中的三大流类,整体遵循「创建流 → 打开文件 → 读写数据 → 关闭文件」的固定流程。
- 文本文件操作简单、可读性好,推荐用 getline(string) 按行读取;
- 二进制文件效率高、体积小,通过 write/read 按字节操作,适合结构化数据持久化。





