C++ 文件读写可视化教学

探索文本与二进制文件操作的两种主要方法:freopen 与 C++ 文件流 (ifstream/ofstream/fstream)

1. 文件操作基本概念

2. 动态演示与交互实验

控制面板

开始 1. 打开/重定向 2. 检查状态 成功 失败 3. 读/写 操作 4. 随机访问 (可选) 5. 关闭/恢复 结束
demo.txt
控制台

结果面板

模拟的文件系统内容会显示在这里...
程序内存中的缓冲区数据...

3. 原理、代码与对比

使用 freopen 进行文件操作 (C 风格)

主要通过重定向标准输入/输出流 (stdin, stdout) 到文件,然后像操作控制台一样使用 scanf/printf 或 C++ 的 cin/cout

// --- 文本写入示例 ---
// 重定向标准输出 stdout 到 "output.txt" 文件
if (freopen("output.txt", "w", stdout) == NULL) {
    perror("错误:无法重定向 stdout");
    exit(1); // 失败则退出
}

// 现在 printf 或 cout 的输出会进入 "output.txt"
printf("这是写入文件的内容。\n");
cout << "使用 cout 写入也可以。" << endl;

// !! 注意:关闭文件通常是通过关闭程序或重定向回控制台
// 恢复 stdout 到控制台 (不同系统方法不同)
freopen("/dev/tty", "w", stdout); // Unix/Linux/macOS
// freopen("CON", "w", stdout); // Windows

// --- 文本读取示例 ---
// 重定向标准输入 stdin 到 "input.txt" 文件
if (freopen("input.txt", "r", stdin) == NULL) {
    perror("错误:无法重定向 stdin");
    exit(1);
}

int number;
char str[100];
// scanf 或 cin 会从 "input.txt" 读取
scanf("%d %s", &number, str);
cin >> number >> str; // 同样有效

// 恢复 stdin (同上)
freopen("/dev/tty", "r", stdin); // Unix/Linux/macOS
// freopen("CON", "r", stdin); // Windows

注意:freopen 直接修改全局流,可能影响程序其他部分,且错误处理不如 C++ 流健壮。

使用 ifstream/ofstream/fstream 进行文件操作 (C++ 风格)

创建文件流对象,关联特定文件进行操作。更符合面向对象思想,提供更丰富的状态检查和控制。

// --- 文本写入示例 (ofstream) ---
ofstream outFile("output.txt"); // 创建输出流对象并尝试打开文件
// 也可以分开: ofstream outFile; outFile.open("output.txt");

if (!outFile.is_open()) { // 检查是否成功打开
    cerr << "错误:无法打开文件 " << "output.txt" << " 进行写入" << endl;
    // 处理错误...
} else {
    outFile << "使用 C++ ofstream 写入文件。" << endl;
    outFile << 12345 << " " << 3.14;
    outFile.close(); // 主动关闭 (对象析构时也会自动关闭)
    if (outFile.fail()) { // 检查关闭/写入过程中是否有错误
        cerr << "写入或关闭文件时发生错误。" << endl;
    }
}

// --- 文本读取示例 (ifstream) ---
ifstream inFile("input.txt"); // 创建输入流对象并尝试打开

if (!inFile) { // 使用重载的 operator! 检查打开和状态
    cerr << "错误:无法打开文件 " << "input.txt" << " 进行读取" << endl;
} else {
    string line;
    // 逐行读取
    while (getline(inFile, line)) {
        cout << "读取到行: " << line << endl;
    }

    // 或按类型读取
    // inFile.clear(); // 如果之前 getline 到达eof, 需要清除状态
    // inFile.seekg(0); // 回到文件开头
    int number;
    string word;
    if (inFile >> number >> word) { // 检查读取是否成功
         cout << "读取到数字: " << number << ", 单词: " << word << endl;
    } else {
        if (inFile.eof()) cerr << "已到文件末尾。" << endl;
        if (inFile.fail()) cerr << "读取格式错误。" << endl;
    }
    inFile.close();
}

简单读取整数对比

假设 input.txt 文件内容为 42

// --- 方法一:freopen + cin ---
#include <iostream>
#include <fstream>// 包含文件流头文件
#include <string>// 如果需要读取字符串等
#include <cstdio> // freopen 在 cstdio 或 stdio.h 中
using namespace std;

int main_freopen() {
    if (freopen("input.txt", "r", stdin) == NULL) {
        perror("freopen 失败");
        return 1;
    }
    int x;
    cin >> x; // 从 input.txt 读取
    cout << "freopen 读取到: " << x << endl; // 输出到控制台 (如果stdout未重定向)

    // 恢复 stdin (如果需要后续控制台输入)
    freopen("/dev/tty", "r", stdin); // 或 "CON"
    return 0;
}

// --- 方法二:ifstream ---

int main_ifstream() {
    ifstream inputFile("input.txt"); // 创建对象并打开

    if (!inputFile) { // 检查打开和初始状态
        cerr << "ifstream 打开失败" << endl;
        return 1;
    }

    int y;
    inputFile >> y; // 从 inputFile 对象读取

    if (inputFile.fail() && !inputFile.eof()) { // 检查读取是否成功 (排除正常EOF)
        cerr << "ifstream 读取失败" << endl;
        inputFile.close();
        return 1;
    }

    cout << "ifstream 读取到: " << y << endl; // 输出到控制台

    inputFile.close(); // 关闭文件
    return 0;
}

总结对比

  • freopen: 简单,代码改动小 (尤其对于习惯用 cin/cout 的场景),但全局影响,错误处理较弱,不面向对象。适合快速原型、算法竞赛。
  • fstream: C++ 标准方式,面向对象,类型安全,错误处理机制完善,功能丰富 (二进制、随机访问等),更适合大型项目和健壮的应用程序。

扩展知识点

  • 二进制文件操作:
    • 打开时加 ios::binary 模式。
    • 使用 read(char* buffer, streamsize count) 读取指定字节数到字符缓冲区。
    • 使用 write(const char* buffer, streamsize count) 从字符缓冲区写入指定字节数。
    • 常用于读写结构体 (struct) 或类对象 (需要注意内存布局和指针)。
      // 写入结构体
      struct Point { int x, y; };
      Point p = {10, 20};
      ofstream binOut("point.dat", ios::binary);
      binOut.write(reinterpret_cast<const char*>(&p), sizeof(Point));
      binOut.close();
      
      // 读取结构体
      Point p_read;
      ifstream binIn("point.dat", ios::binary);
      binIn.read(reinterpret_cast<char*>(&p_read), sizeof(Point));
      binIn.close();
  • 随机访问 (Seeking):
    • seekg(offset, direction): 移动读指针 (Get pointer)。
    • seekp(offset, direction): 移动写指针 (Put pointer)。 (通常读写指针一起移动)
    • tellg() / tellp(): 获取当前读/写指针位置。
    • `direction` 可以是:
      • ios::beg: 从文件开头计算偏移。
      • ios::cur: 从当前位置计算偏移。
      • ios::end: 从文件末尾计算偏移 (通常 offset 为负)。
  • 缓冲区与刷新 (Buffering & Flushing):
    • 文件流通常使用内部缓冲区提高效率,数据不是立即写入物理文件。
    • endl 不仅插入换行符,还会刷新 (flush) 输出缓冲区。
    • file.flush(): 手动强制将缓冲区内容写入文件。
    • 关闭文件 (close() 或对象析构) 会自动刷新缓冲区。
  • 文件打开模式 (Open Modes): 可以用 | (位或) 组合使用
    • ios::in: 读 (ifstream 默认)。
    • ios::out: 写 (ofstream 默认),会清空文件内容。
    • ios::app: 追加 (Append),在文件末尾写入,不清空。
    • ios::binary: 二进制模式。
    • ios::trunc: 截断 (Truncate),打开即清空文件 (ofstream 默认行为之一,除非指定 appin)。
    • ios::ate: (At End),打开后文件指针定位到末尾,但可以移动。