文件操作(文本):fstream、ifstream、ofstream、读写文本文件

文件操作,说白了就是让程序和硬盘上的文件「对话」。我刚开始学C++时,觉得这玩意儿不就是读读写写嘛,有啥难的?直到有一次,我写了一个日志系统,程序跑了三天三夜,结果发现文件句柄没关,内存暴涨——嗯,从那以后我再也不敢小看文件操作了。

今天咱们就聊聊C++里最常用的文本文件操作。你想想看,无论是配置文件、日志记录,还是数据导出,哪个项目能离得开文件读写?

三大文件流类:ifstream、ofstream、fstream

C++标准库提供了三个核心类来处理文件:

  • ifstream:输入文件流,专门用来读文件
  • ofstream:输出文件流,专门用来写文件
  • fstream:文件流,既能读又能写

这三个类都继承自 iostream,所以用法和 cincout 非常像。我个人习惯用 ifstreamofstream 分开操作,这样代码意图更清晰。

核心要点: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;
}

这个例子我在实际项目中用过很多次,简单实用。你想想看,如果不用文件,这些配置就得硬编码在代码里,改一次就得重新编译——多麻烦。

知识体系总览

下面这张图帮你理清今天的内容:

C++ 文本文件操作知识体系 文件操作 ifstream(读) ofstream(写) fstream(读写) 读取方式 • getline() 逐行 • >> 逐词 • get() 逐字符 写入方式 • << 运算符 • write() 方法 • put() 单字符 打开模式 • ios::in / out • ios::app 追加 • ios::trunc 清空 核心:打开 → 检查 → 读写 → 关闭

总结

文件操作其实不难,记住几个关键点就行:

  • ifstream 读,用 ofstream 写,别混用
  • 打开文件后一定要检查是否成功
  • 读文件推荐 getline(),安全可靠
  • 写文件注意模式,别把原有内容覆盖了
  • 用完记得 close(),虽然析构函数会自动关,但养成好习惯

嗯,今天就聊到这儿。文件操作这块儿,多写几次就熟练了。下次遇到读写文件的场景,你心里应该有谱了吧?


公众号:蓝海资料掘金营,微信deep3321