Lesson 0014 · 文件读写(File I/O)

C++ 进阶 — 第 7 课

三句话速览
  1. ofstream 写文件,ifstream 读文件,用法像 cout/cin。
  2. 打开后先 if (!in),打不开立刻退。
  3. 读到没有为止:while (in >> x) 或 getline。
上节课(0013):构造函数负责「出生时初始化」,析构函数负责「临终前清理」。
这节课:让程序把数据存进文件,下次运行还能读回来。游戏存档、成绩单、日志 —— 全靠这个。顺便你会发现,打开文件这件事也是 RAII:流对象离开作用域时自己收尾。

1. 写文件 — ofstream

cout 把数据输出到屏幕;ofstream 把数据输出到文件。用法几乎一样:

#include <fstream>
using namespace std;

int main() {
    ofstream out("scores.txt");     // 打开文件准备写入(不存在会自动创建)

    out << 95 << " " << 87 << endl;   // 和 cout 一模一样的写法
    out << "Alice" << endl;

    out.close();                    // 关闭文件(可选,程序结束也会自动关)
    return 0;
}

看这段代码,和屏幕输出唯一的区别是第一行:把 cout 换成了一个绑定到文件的 out。后面的 <<、endl 一个字都没变。

记忆口诀:output(输出)→ 写文件;input(输入)→ 读文件。

2. 读文件 — ifstream

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

int main() {
    ifstream in("scores.txt");      // 打开文件准备读取

    int a, b;
    string name;
    in >> a >> b >> name;           // 和 cin 一样,按空格/换行分隔
    cout << a << " " << b << " " << name;

    in.close();
    return 0;
}

in >> a >> b >> name 的切分规则和 cin 完全一致:空格、换行都算分隔符,一个 >> 读一个。所以写文件时用什么分隔,读的时候心里要有数。

文件打不开怎么办:文件名写错或文件不存在时,in 是「空」的。养成检查习惯:
if (!in) { cerr << "打不开文件" << endl; return 1; }

3. 一次读一整行 — getline

ifstream in("data.txt");
string line;
while (getline(in, line)) {     // 每次读一行,读到文件结束自动停止
    cout << line << endl;
}

while (getline(in, line)) 和之前学的 while (cin >> x) 是同一个套路:「读到没有为止」。>> 遇空格就停,getline 一行读完 —— 行里有空格就用它。

4. 不知道有多少个数 — 读到文件结束

ifstream in("nums.txt");
int x, sum = 0;
while (in >> x) {    // 读到文件末尾时 in >> x 返回 false,循环结束
    sum += x;
}
cout << sum << endl;

不用先数文件里有几个数:条件里直接 in >> x,读到末尾它会失败,循环自然退出。这是「数据量未知」时的标准写法。

5. 同时读和写 — fstream 与打开模式

默认的 ofstream 打开已存在的文件会直接清空重写。想保留旧内容追加,用 fstream + 打开模式:

#include <fstream>
using namespace std;

int main() {
    fstream f("log.txt", ios::out | ios::app);   // 追加模式:不清空旧内容
    f << "hello" << endl;
    return 0;
}
模式作用
ios::out写文件(默认会覆盖旧内容)
ios::in读文件
ios::app追加(append),不清空
ios::trunc打开时清空文件再写

多个模式用 | 拼起来,读代码时从左往右念就行:ios::out | ios::app = 「要写,而且往末尾接」。

6. 完整示例 — 统计分数并保存结果

假设 scores.txt 每行是一个名字 + 一个分数:

Alice 95
Bob 87
Cindy 92
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream in("scores.txt");
    ofstream out("avg.txt");

    if (!in) {                     // 打不开就退出
        cerr << "打不开 scores.txt" << endl;
        return 1;
    }

    string name;
    int score, sum = 0, cnt = 0;
    while (in >> name >> score) {  // 每次读两个数据
        sum += score;
        cnt++;
    }

    out << "人数: " << cnt << endl;
    out << "平均分: " << (cnt ? sum / cnt : 0) << endl;
    cout << "已写入 avg.txt" << endl;
    return 0;
}

这段是前面所有片段的组合:打开两个流 → 检查打不开 → 循环读 → 循环外统一写。cnt ? sum / cnt : 0 是防除零:文件是空的时候 cnt 为 0,直接除会崩。

7. 一个表看懂四个方向

方向屏幕文件
输出(写)cout << xofstream out("f.txt"); out << x;
输入(读)cin >> xifstream in("f.txt"); in >> x;

记住这张表,就不用背任何新语法:文件流只是把 cout/cin 换了个去向。

8. 坑点清单

9. 小测

Q1. 文件读写需要包含哪个头文件?
Q2. ofstream 打开一个已存在的文件,默认会怎样?
Q3. while (in >> x) 循环什么时候结束?

10. 动手练习

题目:成绩最高分

- 文件 grades.txt:每行一个名字和一个分数
- 读入所有成绩,找出最高分和对应名字
- 写入 best.txt:名字 分数

提示:先看看 while (in >> name >> score) 这个套路。
进阶:如果多个同学并列最高分,全部输出。

卡住时的自查顺序

  1. 文件真的在吗?先看一眼 grades.txt 和程序跑起来的目录是不是同一个。
  2. if (!in) 加了吗?没加的话,先加上再看结果,别猜。
  3. 循环条件写对了吗:while (in >> name >> score),两个变量都要读,少一个就会错位。
  4. 最高分的初值怎么设的?最稳的是「用读到的第一个值初始化」,别拍脑袋写 0。
  5. 写完 best.txt 之后真的打开看一眼 —— 别只信屏幕输出。
  6. 还不对:把 grades.txt 内容、代码、best.txt 一起贴过来。

本课小结

下一课预告
0015 C++ 进阶总复习 + 结课项目 —— 从 0003 到 0014 的知识点列成一张表,然后用一个学生成绩管理程序把它们串起来:读文件、排序、写结果。
你会第一次把 struct、sort、文件读写放进同一个程序里 —— 这节课学的东西,恰恰是结课项目最后一步。

← 上一课 · 课程对照表

视频 黑马第 47-49 集 · 书籍 Primer Plus 第 17 章