文件操作
文件操作基础概念
核心定义
对文件进行读(输入,从文件到程序) 或写(输出,从程序到文件) 的操作,称为文件操作。C++ 标准库提供了封装完善的接口类,简化文件交互流程。
核心头文件
| 头文件 | 作用 |
|---|---|
<fstream> |
包含文件操作核心类(fstream/ifstream/ofstream) |
<ios> |
包含流状态、打开模式等枚举定义 |
<istream> |
输入流基础类(istream) |
<ostream> |
输出流基础类(ostream) |
<streambuf> |
流缓冲区管理类(streambuf) |
<iostream> |
标准输入输出(cin/cout等,文件操作可复用流语法) |
<sstream> |
字符串流类(stringstream等,辅助文件数据处理) |
核心接口类
C++ 文件操作的核心是三个流类,均继承自基础流类,接口统一:
| 类名 | 功能说明 | 构造函数原型 |
|---|---|---|
fstream |
双向流(可读可写) | fstream(const char *filename, openmode mode) |
ifstream |
输入流(仅读) | ifstream(const char *filename, openmode mode) |
ofstream |
输出流(仅写) | ofstream(const char *filename, openmode mode) |
注:
filename为文件路径(相对路径/绝对路径),mode为打开模式(见 1.4)。
文件打开模式
文件打开模式通过 ios 类的枚举值指定,支持组合使用(用 | 连接):
| 模式常量 | 含义 | 适用场景 |
|---|---|---|
ios::in |
以输入模式打开(读文件) | ifstream/fstream |
ios::out |
以输出模式打开(写文件),默认覆盖原有内容 | ofstream/fstream |
ios::app |
追加模式(写文件时在末尾添加,不覆盖) | ofstream/fstream |
ios::binary |
二进制模式(而非文本模式) | 所有流类(读写二进制文件) |
ios::trunc |
若文件存在则清空内容(默认与 ios::out 绑定) |
ofstream/fstream |
ios::ate |
打开文件后定位到文件末尾 | 所有流类 |
常用组合:
- 文本读:
ios::in- 文本写(覆盖):
ios::out或ios::out | ios::trunc- 文本写(追加):
ios::out | ios::app- 二进制读:
ios::in | ios::binary- 二进制写:
ios::out | ios::binary- 读写兼顾:
ios::in | ios::out(fstream专用)
核心类继承关系
ios_base (抽象基类,定义流状态、格式控制)
↓
ios (继承 ios_base,封装流的基本接口)
↓
istream (输入流基类,cin 是其对象)
↓
ifstream (文件输入流)、istringstream(字符串输入流)
↑
iostream (继承 istream + ostream,双向流基类)
↓
ostream (输出流基类,cout/cerr/clog 是其对象)
↓
ofstream (文件输出流)、ostringstream(字符串输出流)
↑
fstream (文件双向流,继承 iostream)、stringstream(字符串双向流)
关键缓冲区概念
- 文件操作默认使用缓冲区(由
streambuf管理),数据不会立即写入文件/读取到程序,而是积累到一定大小后批量处理。 - 手动刷新缓冲区:
flush()(强制刷新)、endl(换行+刷新)。 - 关闭文件时(
close())会自动刷新缓冲区。
文件读取操作(输入:文件 → 程序)
头文件
#include <fstream> // 核心头文件
#include <iostream> // 用于 cout 输出读取结果
#include <cstring> // 用于 strlen 等字符串操作(可选)
using namespace std; // 简化代码(实际项目可按需使用)
构造文件输入类(打开文件)
方法一:构造时直接打开(固定文件)
// 实例化对象时指定文件路径,默认以 ios::in 模式打开
ifstream ifs("./abc.txt");
方法二:先构造再打开(灵活切换文件)
ifstream ifs1; // 仅创建对象,未打开文件
ifs1.open("./even.cpp"); // 后续通过 open() 打开指定文件
// 可重复调用 open() 打开不同文件,但需先 close() 关闭当前文件
读取文件的常用方法
方法一:getline() 读取一行数据
char msg[128]; // 存储读取结果的缓冲区(128字节上限)
// 循环读取,直到文件末尾或出错
while (ifs1.good()) {
// 方式1:读取到换行符 '\n' 为止(默认分隔符)
ifs1.getline(msg, 128);
// 方式2:自定义分隔符(读取到 ']' 为止,不包含分隔符)
// ifs1.getline(msg, 128, ']');
cout << msg << endl;
}
方法二:流提取符 >> 读取(类似 cin)
char msg[128];
while (ifs.good()) {
ifs >> msg; // 以空格/换行符为分隔符,自动跳过空白字符
cout << msg << endl;
}
拓展:读取进阶技巧
先判断文件是否成功打开
ifstream ifs("hero.txt");
if (!ifs.is_open()) { // 或 if (!ifs),流对象可直接作为布尔值
cerr << "文件打开失败!" << endl;
return -1; // 终止程序或处理错误
}
读取二进制文件
// 打开模式需加 ios::binary
ifstream ifs("data.bin", ios::in | ios::binary);
if (!ifs) { cerr << "打开失败" << endl; return -1; }
// 读取指定字节数(例如读取一个 int 类型数据)
int num;
ifs.read((char*)&num, sizeof(int)); // 第一个参数是缓冲区地址(强制转换为 char*)
if (ifs.gcount() == sizeof(int)) { // gcount() 返回实际读取的字节数
cout << "读取到:" << num << endl;
}
get() 与 getline() 的区别
| 函数 | 处理换行符/分隔符 | 空白字符处理 | 适用场景 |
|---|---|---|---|
getline(buf, n) |
丢弃分隔符(不存入buf) | 读取整行(包括空格) | 读取完整文本行 |
get(buf, n) |
分隔符留在输入流中 | 读取到分隔符为止 | 逐段读取(需手动处理分隔符) |
示例:get() 处理分隔符
char ch;
ifs.get(msg, 128, ','); // 读取到 ',' 为止
ifs.get(ch); // 读取并丢弃 ',' 分隔符
文件状态判断(替代 good())
| 成员函数 | 含义 |
|---|---|
eof() |
是否到达文件末尾(End Of File) |
fail() |
操作失败(如读取类型不匹配、缓冲区溢出) |
bad() |
严重错误(如文件损坏、设备错误) |
clear() |
清除流状态标志(恢复操作能力) |
推荐循环写法:
char msg[128];
while (ifs.getline(msg, 128)) { // getline() 成功返回 true,失败返回 false
cout << msg << endl;
}
// 事后判断失败原因
if (ifs.eof()) {
cout << "文件读取完毕" << endl;
} else if (ifs.fail()) {
cout << "读取失败(缓冲区溢出或格式错误)" << endl;
}
文件写入操作(输出:程序 → 文件)
头文件
#include <fstream>
#include <iostream>
#include <cstring>
using namespace std;
构造文件输出类对象(打开文件)
ofstream ofs;
// 打开模式:ios::app(追加) + ios::out(输出),避免覆盖原有内容
ofs.open("abc.txt", ios::app | ios::out);
// 简化写法:构造时直接指定路径和模式
ofstream ofs2("abc2.txt", ios::out | ios::trunc); // 覆盖模式(默认)
写入文件的常用方法
方法一:write() 函数(二进制/文本通用)
const char* msg = "Hello GZ24合班";
// 第一个参数:数据地址,第二个参数:写入字节数
ofs.write(msg, strlen(msg));
方法二:流插入符 <<(类似 cout)
const char* msg = "Hello GZ24合班";
ofs << msg << " eVEN" << endl; // endl 换行+刷新缓冲区
拓展:写入进阶技巧
二进制文件写入
ofstream ofs("data.bin", ios::out | ios::binary);
if (!ofs) { cerr << "打开失败" << endl; return -1; }
int score = 95;
// 写入 int 类型数据(需强制转换为 const char*)
ofs.write((const char*)&score, sizeof(int));
文件关闭的必要性
ofs.close(); // 关闭文件,自动刷新缓冲区,释放文件资源
// 关闭后若需重新写入,需再次调用 open()
- 若未手动关闭,程序结束时会自动关闭,但可能导致缓冲区数据丢失(如异常退出时)。
打开模式组合注意事项
ios::app会强制将写入位置定位到文件末尾,即使后续调用seekp()移动指针也无效。ios::out与ios::trunc绑定,若文件已存在,会先清空内容(除非加ios::app)。- 读写兼顾时(
fstream),需同时指定ios::in和ios::out,否则可能无法正常读写。
缓冲区刷新
ofs << "内容1"; // 仅存入缓冲区,未写入文件
ofs.flush(); // 手动刷新缓冲区,确保数据写入文件
ofs << "内容2" << endl; // endl = 换行 + flush()
拓展示例
- 给英雄添加属性:技能(字符串数组)、攻击力(int)、防御力(int)、血量(int)等。
- 定义
Hero类封装上述属性,提供构造函数、getter/setter 方法。 - 将【英雄名单.txt】(扩展属性后)的内容读取到
Hero类对象中。 - 使用链表存储所有
Hero对象,并实现遍历访问(输出每个英雄的完整信息)。
提示1:Hero 类示例
class Hero {
private:
string name; // 名字
vector<string> skills; // 技能列表
int attack; // 攻击力
int defense; // 防御力
public:
// 构造函数
Hero(string n, vector<string> s, int a, int d)
: name(n), skills(s), attack(a), defense(d) {}
// getter 方法
string getName() const { return name; }
void showInfo() const {
cout << "英雄:" << name << endl;
cout << "技能:";
for (auto& skill : skills) cout << skill << " ";
cout << "\n攻击力:" << attack << " 防御力:" << defense << endl;
}
};
提示2:文件读取到链表示例
#include <list>
list<Hero> heroList; // 链表存储 Hero 对象
// 读取文件内容并创建 Hero 对象
ifstream ifs("英雄名单.txt");
if (!ifs) { cerr << "文件打开失败" << endl; return -1; }
string name, skill;
int attack, defense;
while (ifs >> name >> attack >> defense) { // 假设文件每行格式:名字 攻击力 防御力 技能1 技能2...
vector<string> skills;
while (ifs.peek() != '\n' && !ifs.eof()) { // 读取该行剩余技能
ifs >> skill;
skills.push_back(skill);
}
heroList.emplace_back(name, skills, attack, defense); // 构造对象并加入链表
}
// 迭代器遍历输出
for (list<Hero>::iterator it = heroList.begin(); it != heroList.end(); ++it) {
it->showInfo();
cout << "----------------" << endl;
}

浙公网安备 33010602011771号