以下内容将从文件流类体系、打开模式、文本与二进制 I/O、随机访问、错误处理、性能优化等方面,详解 C++ 中文件输入输出的使用要点,并配以示例。
一、文件流类体系
C++ 标准库提供三种文件流类型,均定义在 <fstream>
中:
std::ifstream
:输入文件流,用于从文件读取(继承自std::istream
)。std::ofstream
:输出文件流,用于向文件写入(继承自std::ostream
)。std::fstream
:读写文件流,可同时做输入和输出(继承自两者)。
它们的典型用法与标准流 (cin
/cout
) 类似,但需要先打开文件。
二、打开模式(std::ios_base::openmode
)
打开时可指定下列模式的按位或(|
)组合:
模式 | 含义 |
---|---|
std::ios::in |
打开用于读操作 |
std::ios::out |
打开用于写操作 |
std::ios::app |
所有写操作均追加到文件末尾 |
std::ios::trunc |
打开时清空已有内容(默认对 ofstream 生效) |
std::ios::binary |
二进制模式(屏蔽文本模式下的换行转换) |
std::ios::ate |
打开后立即定位到文件末尾 |
示例:
std::ifstream fin("data.txt", std::ios::in);
std::ofstream fout("out.txt", std::ios::out | std::ios::trunc);
std::fstream fs("db.bin", std::ios::in|std::ios::out|std::ios::binary);
三、文本文件 I/O
1. 写入文本
operator<<
与格式化std::ofstream fout("log.txt"); fout << "Count = " << count << '\n';
std::endl
与'\n'
std::endl
会插入换行并刷新缓冲,频繁使用有性能开销;推荐输出'\n'
。
2. 读取文本
按词/按行读取
std::ifstream fin("data.txt"); std::string word; while (fin >> word) { /* 按空白分隔 */ } fin.clear(); fin.seekg(0); std::string line; while (std::getline(fin, line)) { /* 读取整行,不含换行符 */ }
混合
>>
与getline
使用>>
后会留下换行符,若紧接getline
,会读入一个空行。
解决:每次切换前调用fin.ignore()
跳过残留的'\n'
。
四、二进制文件 I/O
当读写原始字节或 POD 结构时,选用二进制模式并使用 read
/write
:
struct Record { int id; double value; };
void writeRecords(const std::vector<Record>& recs) {
std::ofstream fout("rec.bin", std::ios::out|std::ios::binary);
fout.write(reinterpret_cast<const char*>(recs.data()),
recs.size()*sizeof(Record));
}
std::vector<Record> readRecords() {
std::ifstream fin("rec.bin", std::ios::in|std::ios::binary);
fin.seekg(0, std::ios::end);
std::size_t size = fin.tellg() / sizeof(Record);
fin.seekg(0, std::ios::beg);
std::vector<Record> recs(size);
fin.read(reinterpret_cast<char*>(recs.data()),
size*sizeof(Record));
return recs;
}
- 注意:直接写整体
vector
只对标准布局(POD)类型安全。若含指针或非平凡类型,需循环写每个字段或序列化。
五、随机访问(定位)
seekg
/seekp
:设置读/写位置fin.seekg(offset, std::ios::beg|std::ios::cur|std::ios::end); fout.seekp(...);
tellg
/tellp
:返回当前位置auto pos = fin.tellg();
注意:文本模式下各种平台会对换行做转换,
seek
/tell
不保证以字节为单位精确跳转;二进制模式下则如你所见。
六、错误处理与异常
1. 状态位检查
if (!fin) { /* 打开失败或已处于错误状态 */ }
if (fin.eof()) { /* 到达末尾 */ }
if (fin.fail()) { /* 格式错误或开关失败 */ }
2. 异常模式
fin.exceptions(std::ios::failbit | std::ios::badbit);
try {
int x;
fin >> x; // 失败时抛 std::ios_base::failure
} catch (const std::ios_base::failure& e) {
std::cerr << "I/O 异常: " << e.what() << "\n";
}
3. 资源管理
文件流析构时会自动
close()
,但若需提前关闭或检测错误,可显式:fin.close(); if (fin.fail()) std::cerr<<"关闭失败\n";
七、性能优化
同步关闭
std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
可加速与 C 风格 I/O 的混用场景。
缓冲区大小
- 默认缓冲区通常足够;若需高性能可自定义:重载流的
rdbuf()->pubsetbuf()
。
- 默认缓冲区通常足够;若需高性能可自定义:重载流的
减少临时与拷贝
- 尽量一次性
read
/write
大块数据; - 对文本分词解析可用
std::string_view
和std::getline
配合std::stringstream
。
- 尽量一次性
避免频繁开关文件
- 对同一文件的多次写入,宜在同一流对象上完成;若交替读写,用
fstream
且调用seek
/flush
而非反复构造流。
- 对同一文件的多次写入,宜在同一流对象上完成;若交替读写,用
八、综合示例
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
struct Record { int id; double value; };
int main() {
// 1. 文本写入
{
std::ofstream log("app.log", std::ios::out|std::ios::app);
if (!log) throw std::runtime_error("无法打开日志");
log << "程序启动\n";
}
// 2. 文本读取与解析
{
std::ifstream fin("config.txt");
if (!fin) {
std::cerr<<"配置文件打开失败\n";
} else {
std::string line;
while (std::getline(fin, line)) {
// 跳过空行和注释
if (line.empty() || line[0]=='#') continue;
std::cout<<"配置: "<<line<<"\n";
}
}
}
// 3. 二进制读写
std::vector<Record> recs = {{1,1.23},{2,4.56},{3,7.89}};
{
std::ofstream fout("data.bin", std::ios::out|std::ios::binary);
fout.write(reinterpret_cast<const char*>(recs.data()),
recs.size()*sizeof(Record));
}
{
std::ifstream fin("data.bin", std::ios::in|std::ios::binary);
fin.seekg(0, std::ios::end);
size_t n = fin.tellg()/sizeof(Record);
fin.seekg(0);
std::vector<Record> buf(n);
fin.read(reinterpret_cast<char*>(buf.data()), n*sizeof(Record));
std::cout<<"读取 "<<buf.size()<<" 条记录\n";
}
return 0;
}
九、注意事项汇总
- 选择合适模式:文本 vs 二进制;是否截断或追加。
- 检查流状态:打开后、读写后、关闭后都应检查
fail()
/bad()
/eof()
。 - 混合读写:使用
std::fstream
并在切换前调用flush()
/seekg()
/seekp()
。 - 异常安全:开启异常模式或自行检测,避免未捕获错误导致数据不一致。
- 平台差异:文本模式下换行转换、字符编码(如 Windows 的 CRLF)可能影响跨平台行为。