"写模式"打开一个不存在的文件,会顺手把它创建出来 ——这是"打开"这个动作的副作用,也是你"先生成一个文件"的最常用手段。
但它的规则有边界:
- 含 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 之前)没有建目录的能力 ——要建目录得用平台相关的方式:
- Windows:
system("mkdir logs")(能用但不优雅,还有安全隐患); - C++17 起:
#include <filesystem>,std::filesystem::create_directories("logs/2026");——这是正路(15.6 会提)。
所以本节的这条坑,只有一条解法:先保证目录存在。
四、实例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
读结果:
- ① 那一行两次运行结果不同 ——第一次"不在"、第二次"在",这正是"创建成功"的证据。建议你连跑两次,亲眼看这个变化。
- ② 的措辞是准确的:此刻文件已经存在(
open就创建了,不等你写)。 - 两次运行的内容都一样:因为
ofstream的默认模式含trunc,第二次运行把第一次的内容清掉重写了(15.2.2)。想看"追加"效果,就把模式改成ios::app。 fileExists这个小工具值得记住:标准 C++ 没有"文件是否存在"的现成函数,"试着打开一下"就是最通用的做法(15.6 会说为什么)。
五、30 秒实验
- 连跑两次,看 ① 那一行从"不在"变"在"。
- 把
ofstream out(fname);改成ofstream out(fname, ios::app);,连跑两次 → 第二次的内容会接在后面(文件里出现两份created at first run)。同一个程序,加一个常量,从"覆盖"变成"累积"。 - 把文件名改成
"sub/created_by_me.txt"(sub目录不存在) → ① 打不开、② 创建失败。记住这句话:会自动建文件,不会自动建目录。 - 把
fileExists里的ifstream换成ofstream→ 它永远返回"在"(因为写模式会把文件建出来)。所以判断"存在"必须用读模式试开。
六、常见误区
- "创建文件需要专门调用某个函数" ——不需要。写模式打开一次就创建了,这是"打开"的副作用。
- "文件是写了内容才产生的" ——是"打开成功"那一刻产生的(0 字节)。所以程序崩了也会留下空文件。
- "目录不存在它也会帮我建" ——不会。标准库只建"文件",不建"目录"(C++17 的
<filesystem>才能建目录)。 - "判断文件是否存在可以用
ofstream试一下" ——绝对不行:写模式会顺手创建它,于是"不存在"变成了"存在"(而且可能把原文件清空)。必须用ifstream(读模式)试开。 - "
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`,或先用系统命令建好)。- ☐ 下一节(15.3 文件的读/写)
相关笔记
- 默认打开方式 —— 上一节:默认值带来的三个后果
- 文件的读-写 —— 下一节:接上流之后怎么读写
- 打开方式 —— 六个常量与决策表
- 删除文件 —— 为什么标准库连"删文件"都得借 C 的函数
- 🔷 C++基础 · 索引 —— 全课路线图