C++ 文件和流(完整指南)

C++ 文件和流:从入门到实战

在 C++ 编程中,数据的输入与输出是程序与外界交互的核心方式。我们平时用 cout 输出信息到控制台,用 cin 读取用户输入,这属于“标准流”操作。但真实世界中的程序往往需要与文件打交道——比如读取配置文件、保存日志、加载用户数据等。这就引出了一个关键概念:C++ 文件和流

你可以把文件想象成一个“仓库”,而流(stream)则是连接程序和仓库的“运输通道”。流不是数据本身,而是数据流动的路径。C++ 通过 fstream 库提供了对文件的读写能力,让你可以像操作控制台一样,对文件进行读写操作。

接下来,我们将一步步带你掌握 C++ 文件和流的核心用法,从基础的打开与关闭文件,到读写文本和二进制数据,再到常见的错误处理和最佳实践。


文件操作的基础:打开与关闭

在 C++ 中,操作文件需要包含头文件 <fstream>,它提供了三个核心类:ifstream(输入流)、ofstream(输出流)和 fstream(通用流,支持读写)。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 创建一个输出流对象,用于写入文件
    std::ofstream outFile("example.txt");

    // 检查文件是否成功打开
    if (!outFile.is_open()) {
        std::cerr << "无法打开文件!" << std::endl;
        return 1;
    }

    // 向文件写入内容
    outFile << "Hello, C++ 文件和流!" << std::endl;
    outFile << "这是第一行。" << std::endl;

    // 必须显式关闭文件,释放资源
    outFile.close();

    std::cout << "文件写入完成。" << std::endl;
    return 0;
}

代码注释说明

  • std::ofstream outFile("example.txt");:创建一个名为 outFile 的输出流对象,并尝试打开 example.txt 文件。
  • if (!outFile.is_open()):检查文件是否打开成功。如果失败,返回错误码。
  • outFile << ...:使用流插入符 << 向文件写入字符串,和 cout 用法一致。
  • outFile.close();:显式关闭文件。这是关键步骤,否则可能造成数据丢失或资源泄露。

⚠️ 提醒:C++ 不会自动关闭文件,必须手动调用 close()。如果忘记关闭,程序退出时系统可能自动回收,但不保证数据已写入磁盘。


读取文件内容:从文本中提取信息

现在我们已经能写入文件,下一步是读取它。这时要用 ifstream 类。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inFile("example.txt");

    // 检查文件是否打开成功
    if (!inFile.is_open()) {
        std::cerr << "无法打开文件用于读取!" << std::endl;
        return 1;
    }

    std::string line;
    // 循环读取每一行,直到文件结束
    while (std::getline(inFile, line)) {
        std::cout << "读取到: " << line << std::endl;
    }

    // 关闭文件
    inFile.close();
    std::cout << "文件读取完成。" << std::endl;
    return 0;
}

代码注释说明

  • std::getline(inFile, line):从输入流中读取一行文本,存入 line 字符串变量。这是读取文本文件最常用的方式。
  • while (std::getline(...)):循环条件自动判断是否读到文件末尾(EOF)。当读取失败时,getline 返回 false,循环结束。
  • 读取时无需手动管理指针或缓冲区,流对象会自动处理。

💡 小贴士:如果你用 inFile >> variable 这种方式读取,会按空格分隔,适合读取单词或数值。但若要读整行,一定要用 getline


文件打开模式:控制行为的关键

C++ 文件操作支持多种打开模式,通过构造函数参数或 open() 方法指定。常见的模式如下:

模式名称 说明
std::ios::in 以只读方式打开文件(默认模式)
std::ios::out 以写入方式打开文件,若文件存在则清空内容
std::ios::app 以追加方式打开,写入内容会加在文件末尾
std::ios::ate 打开文件后定位到文件末尾
std::ios::binary 以二进制模式打开,不进行字符转换

这些模式可以组合使用,用按位或 | 连接。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 以追加模式打开文件,保留原有内容并添加新行
    std::ofstream outFile("log.txt", std::ios::app);

    if (!outFile.is_open()) {
        std::cerr << "无法打开日志文件!" << std::endl;
        return 1;
    }

    outFile << "【时间】2025-04-05 10:30:22 - 用户登录成功" << std::endl;
    outFile.close();

    std::cout << "日志已写入。" << std::endl;
    return 0;
}

使用场景建议

  • 日志文件:用 std::ios::app 模式,避免覆盖旧日志。
  • 配置文件:用 std::ios::in 读取,确保不会意外修改。
  • 二进制数据:如图片、音频,必须用 std::ios::binary 模式。

二进制文件读写:处理非文本数据

当你需要读写图片、音频、序列化数据等非文本内容时,必须使用二进制模式。因为文本模式会处理换行符(如 \n 转为 \r\n),而二进制模式则完全原样读写。

#include <iostream>
#include <fstream>
#include <vector>

int main() {
    // 写入二进制数据
    std::vector<unsigned char> data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello" 的 ASCII 码

    std::ofstream binaryFile("data.bin", std::ios::binary);
    if (!binaryFile.is_open()) {
        std::cerr << "无法创建二进制文件!" << std::endl;
        return 1;
    }

    binaryFile.write(reinterpret_cast<const char*>(data.data()), data.size());
    binaryFile.close();

    std::cout << "二进制数据写入完成。" << std::endl;

    // 读取二进制数据
    std::ifstream readFile("data.bin", std::ios::binary);
    if (!readFile.is_open()) {
        std::cerr << "无法打开二进制文件读取!" << std::endl;
        return 1;
    }

    std::vector<unsigned char> buffer(5); // 预留空间
    readFile.read(reinterpret_cast<char*>(buffer.data()), buffer.size());

    // 检查是否成功读取
    if (readFile.gcount() == buffer.size()) {
        std::cout << "读取到的数据: ";
        for (auto b : buffer) {
            std::cout << std::hex << static_cast<int>(b) << " ";
        }
        std::cout << std::endl;
    } else {
        std::cerr << "读取失败!" << std::endl;
    }

    readFile.close();
    return 0;
}

关键点说明

  • std::ios::binary:必须显式指定,否则行为异常。
  • write()read():用于二进制数据读写,参数是原始字节指针和长度。
  • reinterpret_cast:将 std::vector<unsigned char> 转为 const char*,让 write() 能识别。
  • gcount():返回上次 read() 实际读取的字节数,用于判断是否读完。

错误处理与最佳实践

在真实项目中,文件操作失败是常见情况。网络挂掉、权限不足、路径错误、磁盘满等都会导致失败。因此,必须做好错误处理

#include <iostream>
#include <fstream>
#include <string>

bool safeReadFile(const std::string& filename, std::string& content) {
    std::ifstream file(filename, std::ios::in | std::ios::binary);
    if (!file.is_open()) {
        std::cerr << "错误:无法打开文件 " << filename << std::endl;
        return false;
    }

    // 读取整个文件到字符串
    content.assign((std::istreambuf_iterator<char>(file)),
                   std::istreambuf_iterator<char>());

    file.close();
    return true;
}

int main() {
    std::string data;
    if (safeReadFile("config.txt", data)) {
        std::cout << "文件内容:\n" << data << std::endl;
    } else {
        std::cout << "读取失败,使用默认配置。" << std::endl;
    }

    return 0;
}

最佳实践总结

  • 始终检查 is_open()
  • 使用 RAII(资源获取即初始化)思想,尽量让对象在作用域结束时自动关闭。
  • 避免在 main() 中写过多文件操作逻辑,封装成函数。
  • 使用 std::string 读取文本文件时,注意 std::istreambuf_iterator 的高效性。
  • 二进制文件操作要格外小心,确保字节对齐和类型匹配。

C++ 文件和流的进阶技巧

随着项目复杂度上升,你可能需要:

  • 读写结构体数据:用 write()read() 直接操作内存块。
  • 处理大文件:分块读取,避免内存溢出。
  • 使用 std::filesystem(C++17 起)判断文件是否存在、获取大小、遍历目录。

例如,判断文件是否存在:

#include <filesystem>
#include <iostream>

bool fileExists(const std::string& path) {
    return std::filesystem::exists(path);
}

这比手动打开文件再判断更高效。


结语

C++ 文件和流是构建实用程序的基石。无论是生成日志、加载配置、保存用户数据,还是处理图像、音频等二进制内容,掌握这套机制都至关重要。

从打开文件到读写内容,从文本处理到二进制操作,每一个步骤都需要严谨对待。不要忽视 close() 的调用,不要忽略错误检查,更不要在文件路径上犯错。

真正成熟的 C++ 程序员,不仅会写代码,更懂得如何让代码稳定、健壮、可维护。而这一切,都始于对文件和流的深刻理解。

当你能熟练运用 C++ 文件和流,你就真正迈出了从“会编程”到“写程序”的关键一步。