NOI C++ 课程体系 / 语法入门 / 第9讲
CSP-J 入门级 · 语法入门 · 文件操作

C++ 文件概念与文本文件操作 完全指南

让数据走出内存 —— 文件流、文本文件、二进制文件,一次讲透数据持久化的全部秘密。

6 大教学模块 3 种文件流类 5 个精讲案例 5 大易错点 1 张速查表 15 道分层练习
本讲 · 第 23/56 讲文件概念与文本文件操作
MODULE 01

情景导航

通过小强的"数据丢失"烦恼,引出文件操作的必要性,用四大类比建立直观理解。

📖 故事引入:小强的数据保存烦恼

小强写了一个学生成绩管理系统,程序运行得好好的,输入5个学生的成绩,排序、统计、输出,一切完美。

可是,一关闭程序,所有数据就全没了!下次打开又要重新输入,太麻烦了!

"有没有办法让数据在程序关闭后还能保存下来,下次打开还能用呢?"小强挠了挠头。

核心思路 把数据从内存(断电即失)保存到硬盘上的文件(永久存储),这就是文件操作的意义。

💡 四大类比的智慧

📝

文件 = 笔记本

内存像你的大脑,关机就忘;文件像笔记本,写上去就永久保存。

内存 → 大脑(临时) / 文件 → 笔记本(永久)
📬

文件流 = 水管

数据从程序流向文件,就像水从水管流出。ifstream是进水口,ofstream是出水口。

ifstream ← 读 / ofstream → 写
📞

文件名 = 文件名

就像笔记本要有标签"数学笔记",文件也要有名字,不然找不到。

"score.txt" / "data.bin"
🔒

二进制文件 = 保险柜

文本文件像日记本(人类可读),二进制文件像保险柜(机器原样存储,人类看不懂)。

文本 = 日记 / 二进制 = 保险柜

🛣 生活类比:文件操作的完整流程

打开文件
  • 就像打开一本笔记本,选择"新建"或"打开已有的"
  • 指定文件名和打开方式(读/写/追加)
读写文件
  • 就像在笔记本上写字或看已经写的内容
  • 用 << 写入,用 >> 或 getline 读取
关闭文件
  • 就像写完合上笔记本,释放资源
  • 不关闭会导致数据丢失或文件损坏
文件指针
  • 就像你当前写到第几行,读到的第几行
  • 每次读写后指针自动移动

📄 文件基本概念

文件(File):是计算机中存储在外部介质(如硬盘)上的数据集合,程序关闭后数据仍然存在。

文件流(File Stream):C++ 中用流(Stream)的概念来操作文件。流是一系列字节的有序序列,数据从程序流向文件或从文件流向程序。

字节流抽象:文件被看作一连串的字节,程序按顺序读写这些字节。就像水管里的水,一个一个字节地流过。

EOF(End Of File):文件结束标志。当读取到文件末尾时,流的状态会变为"失败",可以用 eof() 方法检测。

🔑 核心口诀

文件操作口诀 一包含(#include <fstream>)二创建(定义流对象)三打开(open 或构造函数)四读写(<< / >> / getline)五关闭(close)
文本读行用 getline,二进制要加 binary,路径反斜杠要转义,is_open 先检查。
MODULE 02

知识探究

深入理解文件的基本概念、文本文件与二进制文件的区别、文件流三大核心类及操作五步法。

★ 文件的定义与分类

文件是计算机中存储在外部介质上的数据集合,以文件为单位进行数据的读写操作。文件的主要特点:

  • 持久化存储:程序关闭后数据不会丢失
  • 可共享:多个程序可以访问同一个文件
  • 可长期保存:只要硬盘不坏,数据可以永久保存

C++ 中将文件分为两类:

文件类型说明适用场景
文本文件(Text File)以 ASCII / Unicode 编码存储,人类可直接阅读配置文件、日志、数据记录
二进制文件(Binary File)以内存原样格式存储,人类无法直接阅读图片、音频、结构体数据

★ 文件名与路径

文件名:如 "score.txt"、"data.bin"、"log.txt"

路径:文件在硬盘上的位置,如 "C:\\Users\\Admin\\score.txt" 或 "./data/score.txt"

重要提醒 C++ 中字符串里的反斜杠 \ 是转义字符,所以路径中的反斜杠必须写成 \\(双反斜杠)
例如:"C:\\data\\score.txt" 而不是 "C:\data\score.txt"

★ EOF 文件结束符

EOF(End Of File)是文件结束的标志。在文本文件中,EOF 不是一个可见字符,而是一个由操作系统设置的标记。

在 C++ 中,可以通过以下方式检测 EOF:

C++检测 EOF 的方式
#include <iostream>
#include <fstream>

int main() {
    std::ifstream fin("data.txt");
    char ch;
    while (fin.get(ch)) {  // 读取成功返回 true,到 EOF 返回 false
        std::cout << ch;
    }
    return 0;
}

★ 文本文件 vs 二进制文件 详解

对比项文本文件(Text)二进制文件(Binary)
存储格式ASCII / Unicode 编码,每个字符占 1 字节(或更多)内存中的原始二进制格式,按数据类型原样存储
换行符处理自动转换:\n → \r\n(Windows)不转换,原样写入 \n
可读性人类可直接用记事本打开阅读人类无法直接阅读,需要用程序解析
空间效率占用空间较大(如整数 123 存为 "123" 占 3 字节)占用空间较小(如 int 123 占 4 字节)
精度浮点数可能丢失精度(如 3.14159 存为字符串)完全保留原始精度
适用场景配置文件、日志、文本数据图片、音频、视频、结构体/类对象
打开模式默认文本模式必须加 ios::binary 标志
C++文本文件 vs 二进制文件 写入对比
#include <iostream>
#include <fstream>

int main() {
    // 文本文件:整数 123 存为字符 '1','2','3'(3字节)
    std::ofstream fout1("text.txt");
    fout1 << 123;
    fout1.close();

    // 二进制文件:整数 123 按 int 格式存(4字节)
    std::ofstream fout2("binary.bin", std::ios::binary);
    int num = 123;  // 待写入的整数
    fout2.write((char*)&num, sizeof(int));
    fout2.close();

    std::cout << "文本文件写入完成" << std::endl;
    std::cout << "二进制文件写入完成" << std::endl;
    return 0;
}

★ 文件流三大核心类

类名功能继承自主要方法
ifstream从文件读取数据istreamopen(), close(), >>, getline(), read()
ofstream向文件写入数据ostreamopen(), close(), <<, write(), put()
fstream对文件读写iostreamopen(), close(), <<, >>, read(), write()
记忆技巧 input → ifstream(读)  |  output → ofstream(写)  |  file + stream → fstream(读写)

★ 打开模式(Open Modes)

模式标志含义说明
ios::in只读打开文件用于读取(ifstream 默认)
ios::out只写打开文件用于写入(ofstream 默认),会清空原文件内容
ios::app追加在文件末尾追加写入,不会清空原内容
ios::binary二进制模式以二进制方式打开,不加则默认为文本模式
ios::trunc截断打开时清空文件内容(ios::out 默认行为)

组合使用示例:

C++打开模式组合
std::fstream fs;

fs.open("data.txt", std::ios::in);        // 只读
fs.open("data.txt", std::ios::out);       // 只写(清空)
fs.open("data.txt", std::ios::out | std::ios::app);  // 追加写入
fs.open("data.bin", std::ios::binary);   // 二进制模式
fs.open("data.txt", std::ios::in | std::ios::out); // 读写

★ 文件操作五步法

1. 包含 #include <fstream>  →  2. 创建 ifstream / ofstream / fstream  →  3. 打开 open()  →  4. 读写 << / >> / getline  →  5. 关闭 close()
① 包含头文件 ② 定义流对象 ③ 打开文件 ④ 读写操作 ⑤ 关闭文件
C++五步法完整示例
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 第1步:包含头文件 <fstream>
    // 第2步:创建流对象
    std::ofstream fout("hello.txt");

    // 第3步:打开文件(ofstream 构造函数自动打开)
    if (!fout.is_open()) {
        std::cout << "打开文件失败!" << std::endl;
        return 1;
    }

    // 第4步:写入数据
    fout << "Hello, World!" << std::endl;
    fout << "C++ 文件操作" << std::endl;

    // 第5步:关闭文件
    fout.close();

    std::cout << "文件写入完成!" << std::endl;
    return 0;
}

★ 综合案例(5个)

案例1:文本文件写入

C++写入学生成绩到文本文件
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream fout("score.txt");
    if (!fout.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    std::string name;
    int score;
    std::cout << "输入学生姓名和成绩(输入 q 结束):" << std::endl;
    while (std::cin >> name >> score) {
        if (name == "q") break;
        fout << name << " " << score << std::endl;
    }
    fout.close();
    std::cout << "成绩已保存到 score.txt" << std::endl;
    return 0;
}

案例2:逐行读取文本文件

C++逐行读取文件内容
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream fin("score.txt");
    if (!fin.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    std::string line;
    int count = 0;
    std::cout << "=== 文件内容 ===" << std::endl;
    while (std::getline(fin, line)) {  // 逐行读取
        count++;
        std::cout << "第" << count << "行: " << line << std::endl;
    }
    fin.close();
    std::cout << "共读取 " << count << " 行" << std::endl;
    return 0;
}

案例3:格式化读取(姓名+成绩)

C++逐词读取并解析
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream fin("score.txt");
    if (!fin.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    std::string name;
    int score;
    double sum = 0;
    int count = 0;

    while (fin >> name >> score) {  // 按空格/换行分隔读取
        std::cout << name << ": " << score << " 分" << std::endl;
        sum += score;
        count++;
    }

    std::cout << "平均分: " << sum / count << std::endl;
    fin.close();
    return 0;
}

案例4:追加写入(日志记录)

C++追加模式写入日志
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>

int main() {
    std::ofstream fout("log.txt", std::ios::app);  // 追加模式
    if (!fout.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    // 每次运行程序都会追加新日志,不会覆盖旧日志
    std::time_t now = std::time(nullptr);
    fout << "[" << now << "] 程序运行了一次\n";
    fout.close();

    std::cout << "日志已追加到 log.txt" << std::endl;
    return 0;
}

案例5:二进制文件读写结构体

C++二进制方式读写结构体
#include <iostream>
#include <fstream>

struct Student {
    char name[50];  // 定长字符数组:可安全二进制读写
    int score;
};

int main() {
    Student s = {"小明", 95};

    // 二进制写入
    std::ofstream fout("student.bin", std::ios::binary);
    fout.write((char*)&s, sizeof(Student));
    fout.close();

    // 二进制读取
    Student s2;
    std::ifstream fin("student.bin", std::ios::binary);
    fin.read((char*)&s2, sizeof(Student));
    fin.close();

    std::cout << s2.name << ": " << s2.score << std::endl;
    return 0;
}
MODULE 03

实践应用

通过3道综合题,巩固文件读写操作的实际应用能力。

★ 题目1:成绩录入与读取

题目描述:编写程序,从键盘输入5个学生的姓名和成绩,保存到文件 "score.txt" 中。然后重新打开该文件,读取所有学生的成绩,计算平均分并输出。

C++完整代码
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 写入阶段
    std::ofstream fout("score.txt");
    std::string name;
    int score;
    for (int i = 0; i < 5; i++) {
        std::cout << "输入第" << (i+1) << "个学生姓名: ";
        std::cin >> name;
        std::cout << "输入成绩: ";
        std::cin >> score;
        fout << name << " " << score << std::endl;
    }
    fout.close();

    // 读取阶段
    std::ifstream fin("score.txt");
    double sum = 0;
    std::string n;
    int s;
    while (fin >> n >> s) {
        std::cout << n << ": " << s << " 分" << std::endl;
        sum += s;
    }
    fin.close();

    std::cout << "平均分: " << sum / 5 << std::endl;
    return 0;
}
运行结果
输入第1个学生姓名: 小明
输入成绩: 95
输入第2个学生姓名: 小红
输入成绩: 88
输入第3个学生姓名: 小刚
输入成绩: 92
输入第4个学生姓名: 小丽
输入成绩: 90
输入第5个学生姓名: 小强
输入成绩: 85
小明: 95 分
小红: 88 分
小刚: 92 分
小丽: 90 分
小强: 85 分
平均分: 90

★ 题目2:日志追加记录

题目描述:编写程序,模拟日志记录功能。每次运行程序时,向 "log.txt" 追加一条记录,包含时间戳和操作内容。追加3条记录后,读取并输出所有日志。

C++完整代码
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream fout("log.txt", std::ios::app);
    if (!fout.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    std::string ops[] = {"用户登录", "修改密码", "退出系统"};
    for (int i = 0; i < 3; i++) {
        fout << "[LOG] " << ops[i] << std::endl;
    }
    fout.close();

    std::ifstream fin("log.txt");
    std::string line;
    std::cout << "=== 日志内容 ===" << std::endl;
    while (std::getline(fin, line)) {
        std::cout << line << std::endl;
    }
    fin.close();
    return 0;
}
运行结果
=== 日志内容 ===
[LOG] 用户登录
[LOG] 修改密码
[LOG] 退出系统

★ 题目3:文本文件行数统计

题目描述:编写程序,读取文件 "text.txt",统计其中的行数、单词数和字符数,并输出统计结果。

C++完整代码
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

int main() {
    std::ifstream fin("text.txt");
    if (!fin.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }

    int lines = 0, words = 0, chars = 0;
    std::string line, word;

    while (std::getline(fin, line)) {
        lines++;
        chars += line.size();
        std::istringstream iss(line);
        while (iss >> word) words++;
    }
    fin.close();

    std::cout << "行数: " << lines << std::endl;
    std::cout << "单词数: " << words << std::endl;
    std::cout << "字符数: " << chars << std::endl;
    return 0;
}
运行结果(假设 text.txt 内容为 "Hello World\nC++ File")
行数: 2
单词数: 3
字符数: 19
MODULE 04

总结提升

回顾本课核心知识点,掌握文件操作的万能模板,拓展思考方向。

★ 核心要点总结表

知识点关键内容
文件定义存储在硬盘上的数据集合,程序关闭后不丢失
字节流文件被看作连续的字节序列,按顺序读写
文本文件ASCII编码,人类可读,换行符自动转换,适合配置/日志
二进制文件内存原样存储,人类不可读,需加 ios::binary,适合图像/结构体
ifstream从文件读取数据,继承自 istream
ofstream向文件写入数据,继承自 ostream,默认清空原文件
fstream可读写文件,继承自 iostream
五步法包含头文件 → 创建对象 → 打开文件 → 读写 → 关闭
ios::app追加模式,在文件末尾写入,不清空原内容
is_open()检查文件是否成功打开,必须检查!

★ 文件读写万能模板

C++文本文件读写万能模板
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // 写入模板
    std::ofstream fout("output.txt");
    if (!fout.is_open()) {
        std::cout << "写入文件打开失败!" << std::endl;
        return 1;
    }
    fout << "写入内容" << std::endl;
    fout.close();

    // 读取模板
    std::ifstream fin("output.txt");
    if (!fin.is_open()) {
        std::cout << "读取文件打开失败!" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(fin, line)) {
        std::cout << line << std::endl;
    }
    fin.close();
    return 0;
}

★ 思考与启发

💡 思考题

1. 为什么文件操作必须先检查 is_open()?如果不检查会怎样?

2. 文本文件和二进制文件在读写整数时有什么区别?为什么二进制文件更高效?

3. ios::app 和 ios::out 有什么区别?什么场景下用追加模式?

4. 如果忘记关闭文件,会发生什么?为什么?

5. 如何用文件操作实现一个简单的"学生成绩管理系统"?(提示:结合结构体和文件)

MODULE 05

学习检测

检测本课学习成果,掌握5大易错点和速查表,巩固关键知识点。

★ 5大易错点

ERROR 01

忘记检查 is_open() 导致程序崩溃

错误写法
std::ifstream fin("not_exist.txt");
fin >> x;  // 文件不存在,fin 为无效状态
// 直接读取会导致未定义行为
正确写法
std::ifstream fin("not_exist.txt");
if (!fin.is_open()) {
    std::cout << "文件打开失败!" << std::endl;
    return 1;
}
fin >> x;  // 安全读取
ERROR 02

路径中的反斜杠未转义

错误写法
std::ifstream fin("C:\data\score.txt");
// \d 被解释为转义字符,文件名错误
正确写法
std::ifstream fin("C:\\data\\score.txt");
// 双反斜杠 \\\\ 转义为单个反斜杠
// 或使用正斜杠: "C:/data/score.txt"
ERROR 03

ios::out 会清空原文件内容

错误写法
std::ofstream fout("data.txt");
fout << "新内容";
// data.txt 原有内容被清空!
正确写法
std::ofstream fout("data.txt", std::ios::app);
fout << "追加内容";
// 在末尾追加,不清空原内容
ERROR 04

二进制文件忘记加 ios::binary

错误写法
std::ofstream fout("data.bin");
fout.write((char*)&s, sizeof(s));
// 文本模式下换行符会被转换,数据损坏
正确写法
std::ofstream fout("data.bin", std::ios::binary);
fout.write((char*)&s, sizeof(s));
// 二进制模式,数据原样写入
ERROR 05

忘记关闭文件导致数据丢失

错误写法
std::ofstream fout("data.txt");
fout << "重要数据";
// 没有 close(),数据可能还在缓冲区未写入
// 程序结束时自动关闭,但不保证数据已写入
正确写法
std::ofstream fout("data.txt");
fout << "重要数据";
fout.close();  // 确保数据写入磁盘

★ 文件操作速查表

操作代码
包含头文件#include <fstream>
创建只读流ifstream fin("file.txt");
创建只写流(清空)ofstream fout("file.txt");
创建追加流ofstream fout("file.txt", ios::app);
创建二进制流fstream fs("file.bin", ios::binary);
检查文件是否打开if (!fin.is_open()) { ... }
写入数据fout << data;
读取数据fin >> data;
逐行读取getline(fin, line);
二进制写入fout.write((char*)&obj, sizeof(obj));
二进制读取fin.read((char*)&obj, sizeof(obj));
关闭文件fin.close();
检测 EOFif (fin.eof()) { ... }

📌 知识点笔记

📝 文件是什么

存储在硬盘上的数据集合,以文件为单位进行读写。程序关闭后数据不丢失。

📬 文件流

C++ 用流(Stream)操作文件。ifstream 读,ofstream 写,fstream 读写。

📞 文本文件

ASCII 编码,人类可读。换行符自动转换。用 << 和 >> 操作。

🔒 二进制文件

内存原样存储,需加 ios::binary。用 write() 和 read() 操作。效率高。

★ 五步法

包含 → 创建 → 打开 → 读写 → 关闭。缺一不可!

⚠ 注意事项

路径反斜杠转义、is_open 检查、忘记 close 导致数据丢失、二进制必须加 binary。

MODULE 06

练习作业

通过分层练习巩固文件操作知识,从基础到挑战,循序渐进。

★ 总结练习(3题)

基础 文件创建与写入

题目1:创建文件并写入

编写程序,创建一个名为 "hello.txt" 的文件,向其中写入 "Hello, C++ File!",然后关闭文件。最后检查文件是否写入成功。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>

int main() {
    std::ofstream fout("hello.txt");
    if (!fout.is_open()) {
        std::cout << "创建失败!" << std::endl;
        return 1;
    }
    fout << "Hello, C++ File!" << std::endl;
    fout.close();
    std::cout << "文件写入成功!" << std::endl;
    return 0;
}
基础 文件读取

题目2:读取并输出文件内容

假设已有文件 "data.txt",内容为多行文本。编写程序读取该文件并逐行输出到控制台。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

int main() {
    std::ifstream fin("data.txt");
    if (!fin.is_open()) {
        std::cout << "打开失败!" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(fin, line)) {
        std::cout << line << std::endl;
    }
    fin.close();
    return 0;
}
提高 文件综合

题目3:成绩统计与文件输出

从键盘输入5个学生的成绩,保存到 "score.txt" 中,然后读取文件,计算平均分、最高分和最低分,将统计结果写入 "result.txt"。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <algorithm>

int main() {
    int scores[5];
    std::ofstream fout("score.txt");
    for (int i = 0; i < 5; i++) {
        std::cin >> scores[i];
        fout << scores[i] << std::endl;
    }
    fout.close();

    int sum = 0, mx = scores[0], mn = scores[0];
    std::ifstream fin("score.txt");
    int s;
    while (fin >> s) {
        sum += s;
        mx = std::max(mx, s);
        mn = std::min(mn, s);
    }
    fin.close();

    std::ofstream fout2("result.txt");
    fout2 << "平均分: " << sum / 5 << std::endl;
    fout2 << "最高分: " << mx << std::endl;
    fout2 << "最低分: " << mn << std::endl;
    fout2.close();
    return 0;
}

★ A组 · 基础题(4题)

基础 文件写入

题目1:逐词写入文件

从键盘输入若干个单词(以 "end" 结束),将每个单词占一行的方式写入文件 "words.txt"。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream fout("words.txt");
    std::string w;
    while (std::cin >> w && w != "end") {
        fout << w << std::endl;
    }
    fout.close();
    std::cout << "写入完成!" << std::endl;
    return 0;
}

思路:ofstream 打开文件,while (cin >> w && w != "end") 逐词读入并写入文件,最后 close。

易错:忘了 fout.close(),缓冲区没刷新,文件内容可能不完整。

基础 文件读取

题目2:统计文件字符数

读取文件 "text.txt",统计其中的字符总数(包括空格和换行符),并输出结果。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>

int main() {
    std::ifstream fin("text.txt");
    int count = 0;
    char ch;
    while (fin.get(ch)) count++;
    fin.close();
    std::cout << "字符总数: " << count << std::endl;
    return 0;
}

思路:fin.get(ch) 逐字符读取(空格、换行都算),每读到一个计数加一。

易错:用 fin >> ch 读——它会自动跳过空格和换行,统计出来的数偏少。

基础 文件打开模式

题目3:追加模式 vs 清空模式

先以 ios::out 模式写入 "A" 到文件 test.txt,再以 ios::app 模式追加 "B" 到同一文件,最后读取并输出全部内容。观察两次写入的结果。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream fout("test.txt");
    fout << "A";  // ios::out 默认清空
    fout.close();

    std::ofstream fout2("test.txt", std::ios::app);
    fout2 << "B";  // 追加
    fout2.close();

    std::ifstream fin("test.txt");
    std::string s;
    while (fin >> s) std::cout << s;
    fin.close();
    std::cout << std::endl;
    return 0;
}

思路:ios::out(默认)打开会清空原内容,ios::app 在文件末尾追加,用两次写入对比效果。

易错:以为 ofstream 默认就是追加模式,结果原文件内容被清空了。

基础 文件路径

题目4:正确路径写法

以下哪种路径写法是正确的?A. "C:\data\file.txt" B. "C:\\data\\file.txt" C. "C:/data/file.txt" D. B和C都对

查看参考答案

答案:D。B 使用双反斜杠转义,C 使用正斜杠(Windows 也支持),A 中的 \d 会被解释为转义字符,是错误的。

考点:字符串转义——路径中的反斜杠要写成 \\,或直接用正斜杠 /。

易错:以为单个反斜杠能直接写进路径字符串,\d 这类不存在的转义会出错。

★ B组 · 提高题(4题)

提高 文件综合

题目1:成绩管理系统(文件版)

使用结构体 Student(含 name 和 score),实现:从键盘输入3个学生的信息,保存到文件 students.dat(二进制),然后读取并输出所有学生信息。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>

struct Student {
    char name[50];  // 定长字符数组:可安全二进制读写
    int score;
};

int main() {
    Student students[3];
    for (int i = 0; i < 3; i++) {
        std::cin >> students[i].name >> students[i].score;
    }

    std::ofstream fout("students.dat", std::ios::binary);
    for (int i = 0; i < 3; i++) {
        fout.write((char*)&students[i], sizeof(Student));
    }
    fout.close();

    Student s;
    std::ifstream fin("students.dat", std::ios::binary);
    while (fin.read((char*)&s, sizeof(Student))) {
        std::cout << s.name << ": " << s.score << std::endl;
    }
    fin.close();
    return 0;
}

思路:结构体用定长字符数组做成员,就可以安全地 write/read 整块读写。

易错:string 成员直接二进制读写会出问题——它内部存的是指针,不是字符内容。

提高 文件统计

题目2:文本文件单词统计

读取文件 "article.txt",统计每个单词出现的次数,输出出现次数最多的单词及其次数。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>
#include <map>

int main() {
    std::ifstream fin("article.txt");
    std::map<std::string, int> freq;
    std::string w;
    while (fin >> w) freq[w]++;
    fin.close();

    std::string max_word;
    int max_count = 0;
    for (auto &p : freq) {
        if (p.second > max_count) {
            max_count = p.second;
            max_word = p.first;
        }
    }
    std::cout << max_word << ": " << max_count << std::endl;
    return 0;
}

思路:map 记录每个单词出现次数,fin >> w 自动按空白切分单词。

易错:忘了判断文件是否打开成功,文件不存在时程序静默输出空结果。

提高 文件拷贝

题目3:文件拷贝程序

编写程序,将文件 "source.txt" 的内容完整拷贝到 "copy.txt" 中。要求:先检查源文件是否存在,拷贝完成后验证拷贝结果。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>

int main() {
    std::ifstream fin("source.txt");
    if (!fin.is_open()) {
        std::cout << "源文件不存在!" << std::endl;
        return 1;
    }

    std::ofstream fout("copy.txt");
    char ch;
    int count = 0;
    while (fin.get(ch)) {
        fout.put(ch);
        count++;
    }
    fin.close();
    fout.close();

    std::cout << "拷贝完成,共拷贝 " << count << " 字节" << std::endl;
    return 0;
}

思路:先检查源文件 is_open(),再逐字符从 fin 读出、写入 fout 完成拷贝。

易错:不检查 is_open,源文件不存在时静默拷贝出一个空文件。

提高 文件综合

题目4:文本文件行数与单词数统计

读取文件 "data.txt",分别统计行数、单词数和字符数(不含空格),输出统计结果。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

int main() {
    std::ifstream fin("data.txt");
    int lines = 0, words = 0, chars = 0;
    std::string line, w;
    while (std::getline(fin, line)) {
        lines++;
        for (char c : line) {
            if (c != ' ') chars++;
        }
        std::istringstream iss(line);
        while (iss >> w) words++;
    }
    fin.close();
    std::cout << "行数: " << lines << std::endl;
    std::cout << "单词数: " << words << std::endl;
    std::cout << "非空格字符数: " << chars << std::endl;
    return 0;
}

思路:getline 按行读统计行数,再把每行交给 stringstream 拆单词统计词数。

易错:getline 和 >> 混用在同一个流上会互相干扰(>> 留下的换行被 getline 读成空行)。

★ C组 · 挑战题(4题)

挑战 文件综合

题目1:简易学生成绩管理系统

使用结构体 Student(含 id, name, score),实现:(1) 输入 N 个学生的信息并保存到文件 students.dat(二进制);(2) 从文件读取所有学生,按成绩降序排序后输出;(3) 将排序结果写入 sorted.dat。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <algorithm>

struct Student {
    int id;
    char name[50];  // 定长字符数组:可安全二进制读写
    int score;
};

int main() {
    int n;
    std::cin >> n;
    Student *arr = new Student[n];
    for (int i = 0; i < n; i++) {
        std::cin >> arr[i].id >> arr[i].name >> arr[i].score;
    }

    std::ofstream fout("students.dat", std::ios::binary);
    for (int i = 0; i < n; i++) {
        fout.write((char*)&arr[i], sizeof(Student));
    }
    fout.close();

    std::sort(arr, arr + n, [](const Student& a, const Student& b) {
        return a.score > b.score;
    });

    std::ofstream fout2("sorted.dat", std::ios::binary);
    for (int i = 0; i < n; i++) {
        fout2.write((char*)&arr[i], sizeof(Student));
    }
    fout2.close();

    for (int i = 0; i < n; i++) {
        std::cout << arr[i].name << ": " << arr[i].score << std::endl;
    }
    delete[] arr;
    return 0;
}

思路:把学生数据写入文件保存,再读回来排序输出,实现持久化。

易错:new 的数组忘了 delete[];文件用完忘 close 导致数据没写完。

挑战 文件操作

题目2:文件内容反转

读取文件 "input.txt" 的全部内容,将内容完全反转(最后一个字符变成第一个),写入 "output.txt"。例如 "ABC" → "CBA"。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream fin("input.txt");
    std::string content;
    char ch;
    while (fin.get(ch)) content += ch;
    fin.close();

    std::reverse(content.begin(), content.end());

    std::ofstream fout("output.txt");
    fout << content;
    fout.close();
    return 0;
}

思路:把文件全部内容读进一个 string,reverse 后写回另一个文件。

易错:整体反转会把换行符的位置也反过来,行结构被打乱——做题前先确认是按整体反转还是按行反转。

挑战 文件与结构体

题目3:通讯录管理系统

使用结构体 Contact(含 name, phone, email),实现:(1) 从键盘输入若干联系人信息保存到 contacts.dat(二进制);(2) 按姓名搜索联系人并输出;(3) 将所有联系人按姓名排序后输出到控制台。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <cstring>

struct Contact {
    char name[50], phone[20], email[50];  // 定长字符数组:可安全二进制读写
};

int main() {
    std::vector<Contact> contacts;
    std::string name;
    while (std::cin >> name && name != "end") {
        Contact c;
        strcpy(c.name, name.c_str());
        std::cin >> c.phone >> c.email;
        contacts.push_back(c);
    }

    std::ofstream fout("contacts.dat", std::ios::binary);
    for (auto &c : contacts) {
        fout.write((char*)&c, sizeof(Contact));
    }
    fout.close();

    std::sort(contacts.begin(), contacts.end(),
        [](const Contact& a, const Contact& b) {
            return strcmp(a.name, b.name) < 0;
        });

    for (auto &c : contacts) {
        std::cout << c.name << " " << c.phone << " " << c.email << std::endl;
    }
    return 0;
}

思路:vector 存储联系人,用定长字符数组支持二进制读写,按菜单选项增删查。

易错:把 string 成员直接 write 进二进制文件,写入的是指针地址而不是姓名内容。

挑战 文件综合

题目4:日志文件分析器

假设有一个日志文件 "server.log",每行格式为 "时间戳 操作类型 状态码"(如 "2024-01-01 10:00:00 LOGIN 200")。编写程序统计:(1) 总请求数;(2) 每种操作类型的次数;(3) 状态码为 404 和 500 的请求数;(4) 将统计结果写入 report.txt。

查看参考答案
C++参考答案
#include <iostream>
#include <fstream>
#include <string>
#include <map>

int main() {
    std::ifstream fin("server.log");
    int total = 0, err404 = 0, err500 = 0;
    std::map<std::string, int> op_count;
    std::string ts, op, code;

    while (fin >> ts >> op >> code) {
        total++;
        op_count[op]++;
        if (code == "404") err404++;
        if (code == "500") err500++;
    }
    fin.close();

    std::ofstream fout("report.txt");
    fout << "总请求数: " << total << std::endl;
    for (auto &p : op_count) {
        fout << p.first << ": " << p.second << std::endl;
    }
    fout << "404错误: " << err404 << std::endl;
    fout << "500错误: " << err500 << std::endl;
    fout.close();
    return 0;
}

思路:逐行读日志,按空格拆字段,map 统计各操作次数和 404/500 错误数。

易错:用 >> 按空白切分时字段数不固定的日志会错位,要按固定格式仔细对齐。