文件操作(文本):fstream、ifstream、ofstream、读写文本文件
文件操作,说白了就是让程序和硬盘上的文件「对话」。我刚开始学C++时,觉得这玩意儿不就是读读写写嘛,有啥难的?直到有一次,我写了一个日志系统,程序跑了三天三夜,结果发现文件句柄没关,内存暴涨——嗯,从那以后我再也不敢小看文件操作了。
今天咱们就聊聊C++里最常用的文本文件操作。你想想看,无论是配置文件、日志记录,还是数据导出,哪个项目能离得开文件读写?
三大文件流类:ifstream、ofstream、fstream
C++标准库提供了三个核心类来处理文件:
- ifstream:输入文件流,专门用来读文件
- ofstream:输出文件流,专门用来写文件
- fstream:文件流,既能读又能写
这三个类都继承自 iostream,所以用法和 cin、cout 非常像。我个人习惯用 ifstream 和 ofstream 分开操作,这样代码意图更清晰。
核心要点:ifstream 只读、ofstream 只写、fstream 可读可写。别搞混了,否则编译不过去。
打开文件:构造函数 vs open()
有两种方式打开文件。我建议用构造函数,一步到位:
#include <iostream>
#include <fstream>
#include <string>
int main() {
// 方式一:构造函数直接打开
std::ifstream inFile("data.txt");
if (!inFile) {
std::cerr << "打开文件失败!" << std::endl;
return 1;
}
// 方式二:先创建对象,再调用 open()
std::ofstream outFile;
outFile.open("output.txt");
if (!outFile.is_open()) {
std::cerr << "打开文件失败!" << std::endl;
return 1;
}
// 记得关闭
inFile.close();
outFile.close();
return 0;
}
为什么我推荐第一种?因为少写一行代码,少一个出错的机会。我在项目中见过有人忘了调用 open(),结果程序跑起来啥都没写,排查了半天。
注意:打开文件后一定要检查是否成功!用 if (!file) 或 if (!file.is_open()) 都行。我曾经在线上环境遇到过文件路径写错,程序没报错,数据全丢了——血的教训。
读取文本文件:三种常用方式
读文件的方式有好几种,我挑最常用的三个说说:
方式一:逐行读取(推荐)
std::ifstream inFile("data.txt");
std::string line;
while (std::getline(inFile, line)) {
std::cout << line << std::endl;
}
这种方式最安全,不会因为空格或换行符出问题。我99%的项目都用这个。
方式二:逐词读取
std::string word;
while (inFile >> word) {
std::cout << word << std::endl;
}
适合处理以空格分隔的数据,比如配置文件里的键值对。
方式三:逐字符读取
char ch;
while (inFile.get(ch)) {
std::cout << ch;
}
这个用得少,但处理特殊格式时很有用。
小技巧:如果你要读取整个文件到一个字符串,可以用 std::string content((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>()); 一行搞定。不过可读性差了点,我一般只在写工具脚本时用。
写入文本文件:ofstream 的基本用法
写文件比读文件简单多了,和 cout 几乎一样:
std::ofstream outFile("output.txt");
if (!outFile) {
std::cerr << "无法创建文件!" << std::endl;
return 1;
}
outFile << "姓名: 张三" << std::endl;
outFile << "年龄: 25" << std::endl;
outFile << "城市: 北京" << std::endl;
outFile.close();
这里有个坑:默认情况下,ofstream 会覆盖原有文件。如果你想追加内容,需要指定模式:
std::ofstream outFile("log.txt", std::ios::app); // 追加模式
| 模式 | 含义 | 适用场景 |
|---|---|---|
| ios::in | 读模式 | ifstream 默认 |
| ios::out | 写模式(覆盖) | ofstream 默认 |
| ios::app | 追加模式 | 日志文件 |
| ios::ate | 打开后定位到文件尾 | 需要读写文件尾 |
| ios::trunc | 清空文件内容 | 重新写入 |
fstream:读写一体
有时候你需要同时读写同一个文件,这时候 fstream 就派上用场了:
std::fstream file("data.txt", std::ios::in | std::ios::out);
if (!file) {
std::cerr << "打开文件失败!" << std::endl;
return 1;
}
// 先读
std::string line;
std::getline(file, line);
std::cout << "第一行: " << line << std::endl;
// 再写(注意:写操作会从当前位置覆盖)
file << "这是新写入的内容" << std::endl;
file.close();
不过说实话,我很少用 fstream 同时读写。因为文件指针的位置管理起来挺麻烦的,容易出错。我一般先读完,处理完数据,再重新打开写。
避坑指南:用 fstream 同时读写时,记得用 file.seekg() 和 file.seekp() 控制读写位置。我曾经在项目中没注意指针位置,结果读出来的数据全是乱的,排查了整整一个下午。
错误处理与状态检查
文件操作最容易出问题,我总结了几种常见错误:
- 文件不存在:读文件时最常见
- 权限不足:写文件到系统目录时容易遇到
- 磁盘已满:写大文件时要注意
- 路径错误:相对路径和绝对路径搞混
检查文件流状态的方法:
if (file.good()) {
// 一切正常
}
if (file.fail()) {
// 操作失败(比如读取类型不匹配)
}
if (file.bad()) {
// 严重错误(比如磁盘损坏)
}
if (file.eof()) {
// 到达文件尾
}
我个人习惯用 if (!file) 做快速检查,用 file.fail() 做详细诊断。
实战案例:配置文件读写
来,咱们写一个完整的例子。假设我们要读写一个简单的配置文件:
# config.txt
server=192.168.1.1
port=8080
timeout=30
读取配置:
#include <iostream>
#include <fstream>
#include <string>
#include <map>
std::map<std::string, std::string> readConfig(const std::string& filename) {
std::map<std::string, std::string> config;
std::ifstream file(filename);
if (!file) {
std::cerr << "无法打开配置文件: " << filename << std::endl;
return config;
}
std::string line;
while (std::getline(file, line)) {
// 跳过注释和空行
if (line.empty() || line[0] == '#') {
continue;
}
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
config[key] = value;
}
}
file.close();
return config;
}
int main() {
auto config = readConfig("config.txt");
std::cout << "服务器地址: " << config["server"] << std::endl;
std::cout << "端口: " << config["port"] << std::endl;
std::cout << "超时时间: " << config["timeout"] << std::endl;
return 0;
}
这个例子我在实际项目中用过很多次,简单实用。你想想看,如果不用文件,这些配置就得硬编码在代码里,改一次就得重新编译——多麻烦。
知识体系总览
下面这张图帮你理清今天的内容:
总结
文件操作其实不难,记住几个关键点就行:
- 用
ifstream读,用ofstream写,别混用 - 打开文件后一定要检查是否成功
- 读文件推荐
getline(),安全可靠 - 写文件注意模式,别把原有内容覆盖了
- 用完记得
close(),虽然析构函数会自动关,但养成好习惯
嗯,今天就聊到这儿。文件操作这块儿,多写几次就熟练了。下次遇到读写文件的场景,你心里应该有谱了吧?