open() 是"关联"(把流对象和某个文件绑起来),close() 是"分离"(解开绑定)。
理解了这对动作,就明白两件事:
- 一个流对象可以先后关联不同的文件(close 之后 open 新的)——换文件前必须先分离;
- 流对象析构时会自动分离(析构函数调 close,10.3)——这就是"资源交给对象管"在文件上的第一次兑现。
一句话概括:流对象是"插头",open 是插上、close 是拔下——同一个插头能插不同的插座,但要先拔下来。
本节是 15.5~15.6 两节"收尾知识"的第一节,把"生命周期"这件事讲完:
- 10.3 的析构函数在这里正式承担"自动关闭"的职责——14.3 留的 RAII 账,在这里还了第一笔;
- 15.2 的"两种打开时机"在这里有了新的理解:"先建对象、后 open"正是为了"一个对象处理多个文件";
- 15.4.1 的失败状态在这里再次出现:在失败的流上 open 会失败。
一句话:本节讲"流对象的一生"。
一、关联与分离:一对动作
| 动作 | 方法 | 效果 |
|---|---|---|
| 关联 | open("文件") 或构造函数 |
流对象连上一个文件;开始能读写 |
| 分离 | close() |
断开连接;刷新缓冲区;可以再关联别的文件 |
三个要点:
- 不关联就不能读写 ——刚声明、还没
open的流对象是"失败状态"(15.2 的 Demo ② 见过); close()会刷新缓冲区(15.1 / 15.3.1)——所以"写完要关"不只是"礼貌",是"保证数据落地";close()之后对象并没有废,还能再用。
二、一个流对象,先后处理多个文件
这是 open() / close() 存在的最大理由。看这个模式:
ofstream out; // ① 先建"空壳"
for (int i = 0; i < 3; i++) {
out.open(names[i]); // ② 关联
out << "..." << endl;
out.close(); // ③ 分离 —— 换下一个之前必须做
}
为什么必须先 close()?
因为"一个流对象同一时刻只能连一个文件"。 它已经连着一个的时候再
open,标准规定会直接失败(置failbit) ——不会自动帮你关掉前一个。
所以"没关就换"的结果是:新文件根本没被打开,后面的写全部无效、还不报错。
三、析构:忘了 close() 时的最后一道保险
流对象的析构函数会调用 close()(10.3 的机制)——所以你写:
{
ofstream out("a.txt");
out << "hello" << endl;
} // ← 出作用域,析构函数自动 close(顺便刷缓冲)
这在大多数情况下都能正常工作 ——这就是"资源交给对象管"的价值(14.3 留的 RAII 账)。
但为什么还是要养成"显式 close()"的习惯? 三个理由:
| 理由 | 说明 |
|---|---|
| ① 让数据"尽早"落地 | 析构发生在"出作用域"那一刻,中间可能还有很长一段代码,这期间数据都在缓冲里 |
| ② 显式表达意图 | 读代码的人能立刻看到"这里我写完了" |
| ③ 出异常时更可控 | 析构的时机由作用域决定,而 close() 的位置由你决定 |
结论:"靠析构兜底"可以接受,但"显式 close"更好。
四、一个容易被想到的问题:一个文件能被几个流同时打开?
答案:标准没有规定,由实现决定。
实际情况:
- 多个"读"流:通常没问题;
- 一读一写(或两个写):很容易出问题 ——两边各有自己的缓冲区,谁先落地、谁覆盖谁,全看你运气;
- Windows 上更严格:有些情况会直接"拒绝打开"(文件被独占)。
结论:
同一个文件,同一个时刻,只让一个流"拿着"它。 要"一读一写",就在同一个
fstream里做(ios::in | ios::out);要"两个程序同时写",那是另一门学问(文件锁、数据库)。
五、动手 Demo:关联、分离、换文件
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// ══ ① 一个 ofstream 对象,先后写三个文件 ══
{
ofstream out; // 先建空壳
const char *names[3] = {"f1.txt", "f2.txt", "f3.txt"};
for (int i = 0; i < 3; i++) {
out.clear(); // ★ 万一上一轮出过错,先复位
out.open(names[i]); // 关联
if (!out) { cerr << "打开失败: " << names[i] << endl; continue; }
out << "file " << (i + 1) << endl;
out.close(); // 分离
}
cout << "① 一个 ofstream 依次写完了 3 个文件" << endl;
}
// ══ ② 忘了 close 就换文件:open 会失败 ══
{
ofstream out("g1.txt");
out << "first" << endl;
out.open("g2.txt"); // ★ 忘了先 close
cout << "② 没 close 就 open 另一个: fail=" << out.fail()
<< "(失败)" << endl;
out << "second" << endl; // 无效:流已失败
out.close(); // 关掉的是 g1
ifstream a("g1.txt");
string l1; getline(a, l1);
cout << " g1.txt 内容: [" << l1 << "]" << endl;
a.close();
ifstream b("g2.txt");
cout << " g2.txt 存在吗: " << (b ? "在" : "不在(根本没被创建)") << endl;
b.close();
}
// ══ ③ close 之后再 open:正常换文件 ══
{
ofstream out("h1.txt");
out << "hello h1" << endl;
out.close(); // ★ 先分离
out.open("h2.txt"); // ★ 再关联
cout << "③ close 后换文件: fail=" << out.fail() << "(成功)" << endl;
out << "hello h2" << endl;
out.close();
ifstream b("h2.txt");
string l2; getline(b, l2);
cout << " h2.txt 内容: [" << l2 << "]" << endl;
b.close();
}
// ══ ④ rdbuf():一行把整个文件打到屏幕 ══
{
ifstream in("h1.txt");
if (!in) { cerr << "打不开 h1.txt" << endl; return 1; }
cout << "④ rdbuf 直接输出整个文件: " << in.rdbuf();
in.close();
}
return 0;
}
看到什么算成功:
① 一个 ofstream 依次写完了 3 个文件
② 没 close 就 open 另一个: fail=1(失败)
g1.txt 内容: [first]
g2.txt 存在吗: 不在(根本没被创建)
③ close 后换文件: fail=0(成功)
h2.txt 内容: [hello h2]
④ rdbuf 直接输出整个文件: hello h1
读结果:
- ① 是本节的正面示范:一个对象,三次"关联—写—分离",三个文件都写好了。
- ② 是反面教训:
open失败(fail=1)、g2.txt没被创建、后面的写全部无效 ——而且没有任何提示(15.4.1 讲的"点灯不看灯")。注意我在 ① 里写了out.clear()——因为循环里如果上一轮失败过,流会卡住(15.4.1)。 - ③ 证明"先
close再open就能换文件" ——fail=0、新文件有内容。 - ④ 的
in.rdbuf()是"把整个文件塞给输出流"的常用技巧:cout << in.rdbuf();——比逐行读再打印简洁得多(因为它把"文件缓冲"直接接到"屏幕缓冲",中途不经过你的变量)。
六、30 秒实验
- 把 ① 里的
out.clear();删掉,然后故意让f1.txt打不开(比如改成"sub/f1.txt",目录不存在)→ 后面两个文件也都打不开了(流已失败)。这就是"一个坏了,全都废"。 - 把 ② 的
out.open("g2.txt");前面加一句out.close();→ 立刻变成"成功"。一行动作,天壤之别。 - 把 ④ 的
in.rdbuf()换成一个空的ifstream(文件不存在) → 输出为空、也不崩(rdbuf给的是个无效指针,cout <<对它无动作)。别指望它报错。
七、常见误区
- "
close()之后这个对象就作废了" ——没有废。close()只是"分离",对象还能open新文件(Demo ③)。 - "一个流对象同时能连多个文件" ——不能。同一时刻只连一个;连着的时候再
open会失败(Demo ②)。 - "不关就换文件,系统会自动帮我关" ——不会。只会让新的
open失败。 - "靠析构自动关闭就够了" ——大多数时候确实够了,但"显式
close()"更好(数据尽早落地、意图清楚、异常时更可控)。 - "一个文件可以随便用几个流一起读写" ——别这么干。多个写流会互相覆盖(各自的缓冲互不知道)。要同时读写,用一个
fstream。 - "
rdbuf()是给人用的常规写法" ——它是"快捷通道",适合"原样打印整个文件"。要做处理,还是老实用getline/read。
自测三问(点开对答案)
1. **"关联"和"分离"分别对应哪个动作?一个流对象能不能处理多个文件?** ——**`open()` 是关联、`close()` 是分离。** **可以处理多个文件,但必须"先 `close` 再 `open`"** ——**同一时刻只能连一个文件。** 2. **忘了 `close()` 就 `open` 另一个文件,会发生什么?** ——**新的 `open` 会失败(置 `failbit`),新文件不会被创建或打开**,**而后续的读写全部无效、且不报错**。(**且 `close()` 关掉的还是原来那个文件。**) 3. **既然析构函数会自动 `close()`,为什么还要显式关?** ——**① 让数据尽早落地**(不用等到出作用域);**② 把"我写完了"这件事写明白**;**③ 出异常时关闭时机更可控**。**靠析构兜底可以接受,但显式 `close` 更好。**- ☐ 下一节(15.6 删除文件)
相关笔记
- 在指定位置读-写文件 —— 上一节:随机访问
- 删除文件 —— 下一节:标准库为什么不给你删文件的函数
- 文件的打开 —— 两种打开时机
- 析构函数 —— 自动
close()靠的就是它 - 异常处理 —— "资源交给对象管"(RAII)的地基
- 🔷 C++基础 · 索引 —— 全课路线图