第29章:文件与流——fstream、文件模式与stringstream
文件操作,说白了就是让程序跟硬盘上的文件打交道。我刚开始学C++那会儿,总觉得文件读写是个「高级功能」,后来才发现——这玩意儿几乎每个项目都会用到。配置文件的读取、日志的输出、数据的持久化……哪样都离不开它。
这一章,咱们就彻底搞懂C++的文件流。包括ifstream、ofstream、fstream怎么用,文本文件和二进制文件的区别,还有那个特别好用的stringstream。
29.1 文件流的三兄弟:ifstream、ofstream、fstream
C++标准库提供了三个文件流类,都在
- ifstream:输入文件流,用来从文件读数据
- ofstream:输出文件流,用来往文件写数据
- fstream:文件流,既能读也能写
它们都继承自istream/ostream,所以用法跟cin/cout很像。我个人习惯是:如果只需要读,就用ifstream;只需要写,就用ofstream;需要同时读写,才用fstream。这样代码意图更清晰。
核心要点:文件流对象在生命周期结束时,会自动调用close()关闭文件。但显式调用close()是个好习惯——尤其是你要在同一个对象上打开另一个文件时。
29.2 文件打开模式
打开文件时,你可以指定打开模式。这些模式定义在std::ios_base里,用位掩码表示:
| 模式常量 | 含义 | 说明 |
|---|---|---|
| ios::in | 读模式 | 打开文件用于读取 |
| ios::out | 写模式 | 打开文件用于写入(默认会清空文件) |
| ios::app | 追加模式 | 每次写入都追加到文件末尾 |
| ios::ate | 定位到末尾 | 打开后文件指针在末尾,但可以移动 |
| ios::trunc | 截断模式 | 打开文件时清空已有内容 |
| ios::binary | 二进制模式 | 以二进制方式读写,不进行文本转换 |
多个模式可以用|组合。比如:
std::ofstream file("data.txt", std::ios::out | std::ios::app);
这表示以追加模式打开文件。如果文件不存在,会创建它。
注意:ofstream默认就带了ios::out模式。如果你只写ofstream file("test.txt"),它等价于ios::out | ios::trunc——也就是说,文件原有内容会被清空。我曾经因为这个坑,把客户的重要配置文件给覆盖了……从那以后,我每次打开文件都会显式指定模式。
29.3 文本文件读写
文本文件读写是最常见的操作。说白了,就是把数据以人类可读的文本形式存起来。
写入文本文件
#include <fstream>
#include <iostream>
int main() {
std::ofstream outFile("example.txt");
if (!outFile.is_open()) {
std::cerr << "文件打开失败!" << std::endl;
return 1;
}
outFile << "姓名:张三" << std::endl;
outFile << "年龄:25" << std::endl;
outFile << "分数:92.5" << std::endl;
outFile.close();
return 0;
}
你看,跟cout用法一模一样。用<<运算符就行。
读取文本文件
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream inFile("example.txt");
if (!inFile) { // 也可以这样判断
std::cerr << "文件打开失败!" << std::endl;
return 1;
}
std::string line;
while (std::getline(inFile, line)) {
std::cout << line << std::endl;
}
inFile.close();
return 0;
}
这里用getline逐行读取,是最稳妥的方式。为什么?因为用>>读取字符串时,遇到空格就停了。而getline会读取一整行,包括空格。
小技巧:判断文件是否打开成功,可以用if (!file)或者if (!file.is_open())。我个人更推荐前者,因为ifstream重载了bool运算符,更简洁。
29.4 二进制文件读写
二进制文件读写,跟文本文件最大的区别是:没有格式转换。数据在内存里是什么样子,写到文件里就是什么样子。适合存结构体、数组等原始数据。
我做过一个项目,需要把传感器采集的百万级数据点存下来。如果用文本文件,每个浮点数转成字符串再写,不仅慢,文件还大。用二进制读写,速度直接快了10倍。
写入二进制文件
#include <fstream>
#include <iostream>
struct SensorData {
int id;
double value;
char status;
};
int main() {
SensorData data = {1, 23.456, 'A'};
std::ofstream outFile("sensor.bin", std::ios::binary);
if (!outFile) {
std::cerr << "文件打开失败!" << std::endl;
return 1;
}
outFile.write(reinterpret_cast<const char*>(&data), sizeof(data));
outFile.close();
return 0;
}
读取二进制文件
#include <fstream>
#include <iostream>
struct SensorData {
int id;
double value;
char status;
};
int main() {
SensorData data;
std::ifstream inFile("sensor.bin", std::ios::binary);
if (!inFile) {
std::cerr << "文件打开失败!" << std::endl;
return 1;
}
inFile.read(reinterpret_cast<char*>(&data), sizeof(data));
std::cout << "ID: " << data.id << ", 值: " << data.value
<< ", 状态: " << data.status << std::endl;
inFile.close();
return 0;
}
警告:二进制文件在不同平台、不同编译器下可能不兼容。比如结构体对齐、字节序(大端/小端)都会影响数据的布局。如果你要在不同系统间交换数据,最好用文本格式或者定义明确的序列化协议。
29.5 stringstream:内存中的流
stringstream是个好东西。它让你像操作文件流一样操作字符串。说白了,就是把字符串当成一个「虚拟文件」来读写。
它有三个变种:
- istringstream:从字符串读
- ostringstream:往字符串写
- stringstream:既能读也能写
典型用法:字符串拼接
#include <sstream>
#include <iostream>
#include <string>
int main() {
std::ostringstream oss;
oss << "用户ID: " << 1001 << ", 分数: " << 95.5;
std::string result = oss.str();
std::cout << result << std::endl;
return 0;
}
比用+拼接字符串优雅多了,而且性能更好。为什么?因为+会创建临时对象,而ostringstream内部有缓冲区。
典型用法:字符串解析
#include <sstream>
#include <iostream>
#include <string>
int main() {
std::string data = "1001 张三 92.5";
std::istringstream iss(data);
int id;
std::string name;
double score;
iss >> id >> name >> score;
std::cout << "ID: " << id << ", 姓名: " << name
<< ", 分数: " << score << std::endl;
return 0;
}
你看,从字符串里提取数据,就像从文件里读一样简单。
实用技巧:stringstream经常用来做类型转换。比如把int转成string,或者把string转成double。比atoi、itoa安全多了。
29.6 本章知识体系
下面这张图,帮你理清文件与流的核心脉络:
29.7 避坑指南
最后,分享几个我踩过的坑:
- 忘记检查文件是否打开成功——我曾经写了个日志模块,结果磁盘满了,文件没打开,程序还在那疯狂写……最后日志全丢了。从那以后,每次打开文件我都检查is_open()。
- 二进制文件用文本模式打开——在Windows上,文本模式会把\n转成\r\n,二进制数据就全乱了。记住:二进制文件一定要用ios::binary。
- stringstream忘记清空——如果你重复使用同一个stringstream对象,记得调用str("")和clear()。不然上次的内容还在里面。
- 文件路径用反斜杠——在Windows上,路径用\需要转义,比如"c:\\data\\file.txt"。我建议统一用正斜杠"/",跨平台兼容。
嗯,文件与流这部分,说白了就是「打开-读写-关闭」三步走。但细节决定成败。把上面这些点记住了,你写文件操作代码的时候,就能少踩很多坑。
公众号:蓝海资料掘金营,微信deep3321