🔷 第十五章 文件操作
📄 一句话概括

省略 open() 的第二个参数时,每个类都有自己的默认值,而且它们不一样: - ifstream → ios::in(读,且要求文件已存在) - ofstream → ios::out | ios::trunc(写,且会清空!) - fstream → ios::in | ios::out(读写,不清空,但也不建文件) "默认"这两个字背后藏着本章最大的坑:ofstream 的默认会清空文件;fstream 的默认不会创建文件。 一句话概括:默认值是"经过设计的选择",不是"随便定的"——搞清楚它,才能预判你那一行代码到底做了什么。

📝 本节和前面几节的关系

15.2.1 讲了六个常量怎么选,本节回答"不选的时候会怎样": - 本节是全章的"避坑说明书":新手 90% 的"文件内容不见了 / 文件没建出来",根源都在这一节; - 它把 15.2.1 的决策表补完整:你想省事不写模式,就得先知道省略后会是什么; - 15.2.3 紧接着讲"什么时候会自动创建文件"——和本节的 fstream 那条正好接上。 一句话:写文件操作之前,先问自己一句"默认模式是什么"。

一、三个类的默认值(背下来也不冤)

类 默认打开方式 意味什么
ifstream ios::in 只读;文件不存在 → 失败(不创建)
ofstream ios::out \| ios::trunc 只写;文件不存在 → 创建;文件已存在 → 清空!
fstream ios::in \| ios::out 可读可写;不清空;文件不存在 → 失败(不创建)

怎么看这张表最省事:每个类的默认值,就是"这个类最常用来干的事"。

所以 ofstream 的 trunc 不是设计缺陷,是"以'生成文件'为默认场景"的产物。 问题是:当你其实想追加时,它不会问你的意见。

二、默认值带来的三个具体后果

后果一:ofstream 会清空已有文件(最大)

ofstream out("data.txt");        // 等价于 ofstream out("data.txt", ios::out | ios::trunc);

哪怕你后面一个字都没写,只要这一行执行了,文件就空了。

想保留内容,必须显式改:

ofstream out("data.txt", ios::app);              // 追加
ofstream out("data.txt", ios::out | ios::app);   // 同样意思,写全更清楚

后果二:ifstream / fstream 不会创建文件

ifstream in("maybe.txt");        // 文件不存在 → fail(),不创建
fstream  db("maybe.txt");        // 同上(因为默认含 in)

为什么? 因为"读"的前提是"有东西可读" ——打开一个不存在的文件来读,本来就该失败。

所以"读模式不建文件",是合理的;"写模式建文件",也是合理的。

后果三:想用 fstream 读写一个"可能不存在的文件",会比较绕

fstream 默认(in | out)要求文件已经存在。如果文件可能不存在,最省事的做法是"先确保它存在":

// 先"碰"一下文件:用 app 方式打开(不会清空已有内容,也不写任何东西)
{ ofstream touch("data.txt", ios::app); }

// 现在它一定存在了,可以按默认方式读写
fstream db("data.txt", ios::in | ios::out);

这三行的意思是:app 模式既能创建(文件不存在时)、又不会清空(文件存在时) ——所以它是"温和地确保文件存在"的最佳工具。

这就是为什么日志文件永远用 app:它既保证文件在、又不动旧内容。

三、动手 Demo:把三个默认值验证一遍

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// 判断一个文件是不是空的
bool isEmpty(const string &fname) {
    ifstream in(fname);
    string line;
    return !getline(in, line);      // 一行都读不到 = 空
}

int main() {
    // ── ① ifstream 默认:in(要求文件已存在)────
    { ofstream o("exist.txt"); o << "old content" << endl; }   // 备好一个文件

    ifstream i1("exist.txt");
    cout << "① ifstream 开存在的文件  : " << (!i1 ? "失败" : "成功") << endl;
    i1.close();

    ifstream i2("never_created_by_in.txt");
    cout << "① ifstream 开不存在的文件: " << (!i2 ? "失败" : "成功")
         << "(in 不创建文件)" << endl;
    i2.close();

    // ── ② ofstream 默认:out|trunc(会清空)─────
    { ofstream o("exist.txt"); o << "old content" << endl; }   // 重新装回内容
    cout << "② 重开之前 exist.txt 是空的吗: " << (isEmpty("exist.txt") ? "是" : "不是") << endl;

    ofstream o1("exist.txt");        // ★ 默认打开(什么也不写)
    o1.close();
    cout << "② 用默认 ofstream 重开之后   : "
         << (isEmpty("exist.txt") ? "已经被清空!" : "内容还在") << endl;

    ofstream o2("brand_new.txt");    // 不存在的文件
    cout << "② ofstream 开不存在的文件  : " << (!o2 ? "失败" : "成功,并创建了它") << endl;
    o2.close();

    // ── ③ fstream 默认:in|out(不清空、不创建)──
    fstream f1("exist.txt");
    cout << "③ fstream 开存在的文件    : " << (!f1 ? "失败" : "成功(且不清空)") << endl;
    f1.close();

    fstream f2("never_created_by_fstream.txt");
    cout << "③ fstream 开不存在的文件  : " << (!f2 ? "失败" : "成功")
         << "(默认含 in,所以不创建)" << endl;
    f2.close();
    return 0;
}

看到什么算成功:

① ifstream 开存在的文件  : 成功
① ifstream 开不存在的文件: 失败(in 不创建文件)
② 重开之前 exist.txt 是空的吗: 不是
② 用默认 ofstream 重开之后   : 已经被清空!
② ofstream 开不存在的文件  : 成功,并创建了它
③ fstream 开存在的文件    : 成功(且不清空)
③ fstream 开不存在的文件  : 失败(默认含 in,所以不创建)

读结果——三条结论,一一对应上面三个后果:

  1. ① 最后一行失败:ifstream 默认只读不建。
  2. ② 中间那行最关键:ofstream o1("exist.txt"); 什么都没写,文件却空了 ——这就是默认 trunc 的实锤。
  3. ③ 最后一行失败:fstream 默认含 in,所以它也不会创建文件。

四、30 秒实验

  1. 把 ② 里的 ofstream o1("exist.txt"); 改成 ofstream o1("exist.txt", ios::app); → 内容还在。默认值一变,命运就变了。
  2. 把 ③ 第一段改成 fstream f1("exist.txt", ios::in | ios::out | ios::trunc); → 文件被清空。证明"想清空得明说"。
  3. 在 ofstream o2("brand_new.txt"); 那行之后,去文件夹里看一眼 → brand_new.txt 真的出现了(0 字节)。证明"写模式打开不存在的文件=创建它"(并且它现在是空的,因为还没写东西)。

五、常见误区

  1. "三个类的默认模式一样" ——不一样,而且差别很大:in / out|trunc / in|out。
  2. "ofstream 只是打开来写,不会动旧内容" ——默认会清空(含 trunc)。这是本章最容易造成数据丢失的一条。
  3. "fstream 是万能的,读写文件都用它" ——它默认既不创建也不清空,在"生成新文件"的场景下反而更麻烦。按用途选类:只读用 ifstream、只写用 ofstream、要改已有文件才用 fstream。
  4. "fstream 打开不存在的文件会自动创建" ——不会(默认含 in)。要么先"温和地建一个"(ofstream + app 开一下),要么显式加 out。
  5. "默认值那么坑,那我每次都把六个常量全写一遍算了" ——没必要。只在你真正在意的地方写清楚:写文件时写清 app 还是 trunc;fstream 时写清 in|out。 其他情况默认值就是对的。
自测三问(点开对答案) 1. **`ifstream` / `ofstream` / `fstream` 的默认打开方式分别是什么?** ——**`ios::in`** / **`ios::out \| ios::trunc`** / **`ios::in \| ios::out`**。 2. **为什么 `ofstream out("a.txt");` 会把 `a.txt` 的内容清掉?** ——**因为 `ofstream` 的默认模式里含 `trunc`(截断)**。**想保留内容必须显式写 `ios::app`。** 3. **文件可能不存在,但你想用 `fstream` 读写它,稳妥的做法是什么?** ——**先用 `ofstream touch("a.txt", ios::app);` 温和地确保文件存在**(`app` 既能创建、又不清空),**然后再 `fstream db("a.txt", ios::in | ios::out);`**。

相关笔记