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

"写模式"打开一个不存在的文件,会顺手把它创建出来 ——这是"打开"这个动作的副作用,也是你"先生成一个文件"的最常用手段。 但它的规则有边界: - 含 out(或 app / trunc)→ 会创建; - 只含 in → 不会创建(读一个不存在的东西,本来就该失败); - 目录不存在 → 一定失败(会建文件,但永远不会帮你建目录)。 一个容易忽略的细节:文件在 open 成功的那一刻就已经存在了(0 字节),哪怕你一个字都没写。 一句话概括:"创建文件"不需要专门的命令——把写模式打开一次,文件就有了。

📝 本节和前面几节的关系

本节是 15.2 组的收尾,把前两节的知识落成一个能跑的程序: - 15.2.1 的 ios::out | ios::trunc 和 15.2.2 的"默认模式"在这里合成一个具体后果; - 本节要顺手回答一个新手常见疑问:"我明明写的是创建文件,为什么它说打不开?" ——答案通常是"目录不存在"; - exists() 这个"用 ifstream 试开"的小技巧,是"标准 C++ 里判断文件是否存在"的经典写法(15.6 会讲为什么标准库不给你现成的开关)。 一句话:本节 = 15.2 组三节知识的第一次合练。

一、创建文件的规则:看模式,不看你的意图

打开模式 文件不存在时 说明
ios::out 创建 底层相当于 "w"
ios::out \| ios::trunc 创建 同上(ofstream 的默认)
ios::out \| ios::app 创建 底层相当于 "a"——"追加"的前提是文件得在,所以它会建
ios::in 失败 底层相当于 "r"——读必须有东西
ios::in \| ios::out 失败 底层相当于 "r+"——同样是"读"的要求

一句话总结:只要模式里有"写"的意图(out / app / trunc),就会创建;只有 in 就不会。

为什么这么定? 因为这两件事的性质不同:

二、文件是哪一刻被创建的

不是"写第一笔数据时",而是"open 成功时"。

ofstream out("new.txt");      // ← 这行执行完,new.txt 已经存在了(0 字节)
// 后面就算一个字都不写,文件也在

验证方法:跑完上面两行,去文件夹里看——new.txt 已经在那儿了,大小 0 KB。

这解释了一个现象:

程序"跑了一半崩了",文件夹里却留下了几个空文件 ——因为它们在 open 那一刻就诞生了。

三、唯一会失败的情况:目录不存在

ofstream out("logs/2026/run.txt");     // 如果 logs/2026 不存在 → 打开失败

为什么会失败? 因为操作系统只负责"在某个已存在的目录里创建文件",不负责"沿路把目录都建出来"。

标准 C++(C++17 之前)没有建目录的能力 ——要建目录得用平台相关的方式:

所以本节的这条坑,只有一条解法:先保证目录存在。

四、实例02:创建文件

教材这道题要做的:写一个程序,创建一个文件、写点内容、再读回来验证。

同时演示一个必备小工具——"判断文件在不在"。

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

// ── 小工具:用"试着以读方式打开"来判断文件是否存在 ──
bool fileExists(const string &fname) {
    ifstream in(fname);          // 打得开 → 说明在
    return (bool)in;
}

int main() {
    const string fname = "created_by_me.txt";

    cout << "① 创建前,文件存在吗: " << (fileExists(fname) ? "在" : "不在") << endl;

    // ── 用写模式打开 → 不存在就顺带创建 ──
    ofstream out(fname);
    if (!out) {                  // 唯一可能失败的原因:目录不存在 / 没权限
        cerr << "创建失败(检查目录是否存在、有没有写权限)" << endl;
        return 1;
    }
    cout << "② 打开成功,文件此刻已经存在" << endl;

    out << "created at first run" << endl;
    out << "line 2" << endl;
    out.close();

    cout << "③ 写完关闭后再查: " << (fileExists(fname) ? "在" : "不在") << endl;

    // ── 读回来确认 ──
    ifstream in(fname);
    if (!in) { cerr << "读不了" << endl; return 1; }
    string line;
    while (getline(in, line))
        cout << "   读到: " << line << endl;
    in.close();
    return 0;
}

看到什么算成功(第一次运行):

① 创建前,文件存在吗: 不在
② 打开成功,文件此刻已经存在
③ 写完关闭后再查: 在
   读到: created at first run
   读到: line 2

第二次运行(文件已经在),输出变成:

① 创建前,文件存在吗: 在
② 打开成功,文件此刻已经存在
③ 写完关闭后再查: 在
   读到: created at first run
   读到: line 2

读结果:

五、30 秒实验

  1. 连跑两次,看 ① 那一行从"不在"变"在"。
  2. 把 ofstream out(fname); 改成 ofstream out(fname, ios::app);,连跑两次 → 第二次的内容会接在后面(文件里出现两份 created at first run)。同一个程序,加一个常量,从"覆盖"变成"累积"。
  3. 把文件名改成 "sub/created_by_me.txt"(sub 目录不存在) → ① 打不开、② 创建失败。记住这句话:会自动建文件,不会自动建目录。
  4. 把 fileExists 里的 ifstream 换成 ofstream → 它永远返回"在"(因为写模式会把文件建出来)。所以判断"存在"必须用读模式试开。

六、常见误区

  1. "创建文件需要专门调用某个函数" ——不需要。写模式打开一次就创建了,这是"打开"的副作用。
  2. "文件是写了内容才产生的" ——是"打开成功"那一刻产生的(0 字节)。所以程序崩了也会留下空文件。
  3. "目录不存在它也会帮我建" ——不会。标准库只建"文件",不建"目录"(C++17 的 <filesystem> 才能建目录)。
  4. "判断文件是否存在可以用 ofstream 试一下" ——绝对不行:写模式会顺手创建它,于是"不存在"变成了"存在"(而且可能把原文件清空)。必须用 ifstream(读模式)试开。
  5. "ifstream 打开失败就是文件不存在" ——不一定。没有读权限、文件被独占占用、路径写错、是个目录……都会失败。标准 C++ 不告诉你具体原因——想区分要靠平台 API。
自测三问(点开对答案) 1. **什么情况下打开文件会"顺带创建"它?什么情况下不会?** ——**模式里含"写"的意思(`out` / `app` / `trunc`)时会创建**;**只含 `in` 时不会**(读一个不存在的文件本来就该失败)。 2. **文件是在"打开时"创建还是"写第一笔数据时"创建?** ——**在 `open` 成功的那一刻**。**所以哪怕一个字都不写,文件也已经存在(0 字节)** ——**这也是"程序中途崩了,却留下空文件"的原因。** 3. **`ofstream out("logs/run.txt");` 失败,最可能的原因是什么?怎么解决?** ——**`logs` 目录不存在**(标准库会建文件、但不建目录)。**解决:先保证目录存在**(Windows 上建目录可用 C++17 的 `std::filesystem::create_directories`,或先用系统命令建好)。

相关笔记