在C++编程中,文件操作是最基础也最常用的功能之一。ifstream和ofstream这两个类就像是程序员与文件系统之间的"翻译官",负责在内存和磁盘之间建立数据通道。我第一次接触文件操作是在大学的数据结构课上,当时需要把算法运行结果保存到文件中,却因为没理解清楚流的概念导致数据全部丢失,这个教训让我深刻认识到掌握文件流的重要性。
ifstream(input file stream)是专门用于文件读取的输入流类,相当于一个从文件到程序的数据管道。当我们需要读取配置文件、分析日志文件或加载资源时,都会用到它。而ofstream(output file stream)则是输出流类,负责把程序中的数据写入文件,比如保存用户设置、记录程序状态或导出计算结果。
这两个类都定义在
创建一个ifstream对象并打开文件的标准写法如下:
cpp复制#include <fstream>
#include <string>
std::ifstream inputFile("data.txt"); // 创建时直接打开文件
if (!inputFile.is_open()) { // 必须检查是否打开成功
// 错误处理
}
文件打开后,我们有多种读取方式可选。对于结构化数据,最直观的是使用提取运算符>>:
cpp复制int value;
std::string text;
inputFile >> value >> text; // 自动类型转换,按空格分隔
对于按行读取的场景(比如处理日志文件),getline是更好的选择:
cpp复制std::string line;
while (std::getline(inputFile, line)) {
// 处理每一行
}
关键提示:文件操作必须进行错误检查!我见过太多因为没检查文件状态导致的bug。除了is_open(),good()、fail()等状态函数也要合理使用。
ifstream支持更精细的文件控制,比如设置读取位置:
cpp复制// 定位到文件末尾前100字节处
inputFile.seekg(-100, std::ios_base::end);
对于大型文件,我们可以通过调整缓冲区大小来提高性能:
cpp复制char buffer[1024*1024]; // 1MB缓冲区
inputFile.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
二进制读取模式可以保留原始数据格式,常用于非文本文件:
cpp复制struct Record {
int id;
double value;
};
Record rec;
inputFile.read(reinterpret_cast<char*>(&rec), sizeof(Record));
文件路径问题:相对路径是相对于程序运行目录的,不是源代码目录。建议使用绝对路径或统一设置工作目录。
编码问题:Windows下中文路径可能需要使用std::wstring和wifstream:
cpp复制std::wifstream wfile(L"中文路径.txt");
ofstream的基本使用与ifstream类似,但方向相反:
cpp复制std::ofstream outputFile("output.txt");
if (!outputFile) { // 重载了bool操作符,可直接判断
// 错误处理
}
outputFile << "当前时间: " << std::time(nullptr) << "\n";
写入模式控制非常重要,常用的打开模式包括:
cpp复制// 以追加模式打开
std::ofstream logFile("app.log", std::ios::app);
ofstream支持丰富的格式化控制,比如:
cpp复制outputFile << std::fixed << std::setprecision(2) << 3.14159; // 输出3.14
对于结构化数据,可以考虑使用JSON等格式:
cpp复制outputFile << "{\n"
<< " \"name\": \"" << userName << "\",\n"
<< " \"score\": " << score << "\n"
<< "}";
cpp复制std::ostringstream buffer;
buffer << "大量数据..." << moreData;
outputFile << buffer.str();
cpp复制outputFile << "重要数据" << std::flush; // 立即写入磁盘
cpp复制outputFile.exceptions(std::ios::failbit | std::ios::badbit);
典型的INI格式配置文件处理:
cpp复制std::map<std::string, std::string> config;
// 读取配置
std::ifstream confFile("settings.ini");
std::string key, value;
while (confFile >> key >> value) {
config[key] = value;
}
// 写入配置
std::ofstream outConf("settings.ini");
for (const auto& [key, value] : config) {
outConf << key << " = " << value << "\n";
}
简单的日志类设计:
cpp复制class Logger {
std::ofstream logFile;
public:
Logger(const std::string& filename) : logFile(filename, std::ios::app) {}
template<typename T>
Logger& operator<<(const T& message) {
auto now = std::chrono::system_clock::now();
logFile << std::chrono::system_clock::to_time_t(now)
<< ": " << message << "\n";
return *this;
}
};
// 使用示例
Logger debugLog("debug.log");
debugLog << "程序启动,版本号" << version;
游戏存档的二进制读写:
cpp复制struct GameSave {
int level;
float health;
char name[32];
};
// 保存
GameSave save {5, 75.5f, "Player1"};
std::ofstream saveFile("save.dat", std::ios::binary);
saveFile.write(reinterpret_cast<char*>(&save), sizeof(GameSave));
// 读取
GameSave loaded;
std::ifstream loadFile("save.dat", std::ios::binary);
loadFile.read(reinterpret_cast<char*>(&loaded), sizeof(GameSave));
利用构造函数和析构函数自动管理文件资源:
cpp复制class FileHandle {
std::fstream file;
public:
FileHandle(const std::string& name, std::ios::openmode mode)
: file(name, mode) {
if (!file) throw std::runtime_error("文件打开失败");
}
~FileHandle() { if(file.is_open()) file.close(); }
// 禁止拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 允许移动
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
operator std::fstream&() { return file; }
};
// 使用示例
{
FileHandle tmpFile("temp.txt", std::ios::out);
tmpFile << "临时数据";
} // 离开作用域自动关闭文件
cpp复制std::filesystem::path filePath("folder/file.txt"); // C++17
文本模式转换:Windows下"\r\n"与Unix下"\n"的自动转换可能导致二进制文件问题。
文件权限:Linux下可能需要设置权限:
cpp复制const int perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
int fd = open("file.txt", O_CREAT | O_WRONLY, perm);
std::ofstream outFile;
outFile.__open(fd, std::ios::out);
全面的错误检查应该包括:
cpp复制std::ifstream file("data.bin", std::ios::binary);
if (!file.is_open()) {
// 打开失败
} else if (file.bad()) {
// 不可恢复错误
} else if (file.fail()) {
// 格式错误等可恢复错误
file.clear(); // 清除错误状态
} else if (file.eof()) {
// 正常到达文件末尾
}
// 检查文件大小
file.seekg(0, std::ios::end);
auto size = file.tellg();
if (size == -1) {
// 获取大小失败
}
基准测试通常显示:
对于超大文件,考虑使用内存映射:
cpp复制#include <sys/mman.h>
#include <fcntl.h>
int fd = open("large.bin", O_RDONLY);
void* data = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0);
// 使用后...
munmap(data, fileSize);
close(fd);
Boost.Filesystem:提供更强大的文件系统操作(现已成为C++17标准)
zstr:支持透明压缩的文件流
RapidJSON/rapidxml:专门的文件解析库
在实际项目中,我发现根据具体需求选择合适的工具最重要。对于简单的配置文件,标准库完全够用;对于复杂的结构化数据,专门的序列化库可能更合适。