Appearance
第8章 文件操作
🎯 本章学习目标
通过本章的学习,你将能够:
- 理解文件作用:知道文件用于持久化存储数据,解决内存数据易失的问题
- 掌握freopen重定向:熟练使用 freopen 进行输入输出重定向
- 掌握文件打开模式:理解 r/w/a 等模式的含义和区别
- 使用C++文件流:会用 ifstream 读文件、ofstream 写文件
- 使用C风格文件:了解 fopen/fclose/fscanf/fprintf 的用法
- 正确判断文件末尾:掌握 while (fin >> x) 的正确写法,避免 eof() 陷阱
- 区分覆盖与追加:知道默认写模式是覆盖,ios::app 是追加
- 检查打开成功:养成打开文件后检查 is_open() 的好习惯
- 正确关闭文件:理解 close() 的作用,知道为什么要关闭文件
- 处理路径问题:知道 Windows 路径中反斜杠需要转义
🗺️ 本章知识图谱
文件操作
├── 文件基础
│ ├── 为什么需要文件(持久化)
│ ├── 文本文件 vs 二进制文件
│ └── 文件路径(绝对/相对)
├── freopen 重定向
│ ├── freopen 三参数
│ ├── 重定向 stdin
│ ├── 重定向 stdout
│ └── 竞赛常用写法
├── C++ 文件流
│ ├── ifstream 读文件
│ ├── ofstream 写文件
│ ├── fstream 读写
│ ├── is_open() 检查
│ ├── close() 关闭
│ ├── >> 和 << 操作
│ ├── getline 读一行
│ └── ios::app 追加模式
├── C 风格文件
│ ├── FILE* 指针
│ ├── fopen/fclose
│ ├── fscanf/fprintf
│ ├── fgetc/fputc
│ └── EOF 判断
├── 常见模式
│ ├── "r" 只读
│ ├── "w" 只写(覆盖)
│ ├── "a" 追加
│ ├── "rb"/"wb" 二进制
│ └── ios::in / ios::out / ios::app
├── 易错点
│ ├── 不检查打开成功
│ ├── 忘记关闭文件
│ ├── 覆盖 vs 追加搞混
│ ├── eof() 的错误用法
│ ├── >> 与 getline 混用
│ └── 路径反斜杠转义
└── 典型应用
├── 读写整数/字符串
├── 文件统计(字符/行数)
├── 学生成绩管理
└── 竞赛数据输入输出一、为什么需要文件操作?
我们之前写的所有程序,数据都存放在变量和数组中,存在三个明显问题:
| 问题 | 说明 |
|---|---|
| 数据易失 | 程序运行结束后内存释放,所有数据消失 |
| 输入不便 | 大量测试数据只能逐行从键盘敲入,效率低、易出错 |
| 结果无法保存 | 输出只在屏幕上一闪而过,无法后续查看或复用 |
文件操作恰好能解决这三个痛点:
- 持久化存储——数据写入磁盘文件,程序关闭后依然存在
- 批量输入——从文件读取大量数据,无需手动输入
- 结果保存——将程序输出写入文件,方便查阅和提交
🔥 GESP考点说明:GESP四级主要考查文本文件的读写操作,涵盖C风格文件操作(
FILE*)、C++风格文件流(fstream)以及freopen重定向三大方式。二进制文件仅需概念了解。
二、文本文件与二进制文件
在开始写代码之前,先搞清楚文件存储的两种基本形式。
| 对比项 | 文本文件 | 二进制文件 |
|---|---|---|
| 存储内容 | 字符的 ASCII/UTF-8 编码 | 数据在内存中的原始二进制表示 |
| 可读性 | 可用记事本直接打开阅读 | 需要特定程序解析,直接打开是乱码 |
| 换行符 | Windows 下为 \r\n,Linux 下为 \n | 无换行概念,原样存储字节 |
| 数值 123 的存储 | 三个字符 '1' '2' '3'(字节 49 50 51) | 整数 123 的 4 字节二进制表示 |
| 典型扩展名 | .txt、.csv、.cpp | .exe、.jpg、.dat |
| GESP要求 | ⭐⭐⭐ 重点掌握 | 仅了解概念 |
⚠️ 易错点:用
fprintf或ofstream写入整数123时,文本文件中存的是三个字符'1'、'2'、'3'(ASCII码分别为 49、50、51),共3个字节;而用fwrite以二进制方式写入,存的是整数 123 的 4 字节补码表示。考试常考判断题!
三、文件重定向(freopen)
3.1 什么是重定向?
默认情况下:
cin/scanf从键盘读取数据(标准输入流stdin)cout/printf向屏幕输出数据(标准输出流stdout)
重定向就是把标准输入/输出"指向"一个文件,之后所有的 cin/cout(或 scanf/printf)操作自动变为文件读写,业务代码无需任何修改。
3.2 freopen 函数原型
cpp
#include <cstdio> // freopen 所在头文件
FILE* freopen(const char* filename, const char* mode, FILE* stream);
// ↑ ↑ ↑
// 文件名/路径 打开模式 被重定向的流常用调用方式:
cpp
freopen("input.txt", "r", stdin); // 标准输入重定向到 input.txt
freopen("output.txt", "w", stdout); // 标准输出重定向到 output.txt3.3 完整示例
cpp
#include <iostream>
#include <cstdio> // freopen 头文件
using namespace std;
int main() {
// 将标准输入重定向到 input.txt
freopen("input.txt", "r", stdin);
// 将标准输出重定向到 output.txt
freopen("output.txt", "w", stdout);
// 之后的代码与平时完全一样!无需修改 cin/cout
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}假设 input.txt 内容为:
3 5程序运行后,output.txt 中会写入:
83.4 竞赛中的使用技巧
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
// 本地调试时使用文件输入输出
// 提交到OJ时注释掉这两行即可
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int n;
cin >> n;
cout << n * n << endl;
return 0;
}💡 实用技巧:很多OJ(包括GESP机考环境)会自动将标准输入/输出绑定到测试数据文件,保留
freopen语句通常不影响评测。但更稳妥的做法是用条件编译,提交时无需修改代码:cpp#ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif编译时加上
-DLOCAL选项即可启用本地文件重定向。
⚠️ 易错点:
freopen的第二个参数是C风格字符串(双引号),如"r"、"w";而后面要学的fstream的打开模式是常量,如ios::in、ios::out。两者不要混淆,这是选择题常见陷阱!
四、C风格文件操作
C风格文件操作围绕 FILE*(文件指针)展开,定义在 <cstdio> 头文件中。虽然C++提供了更面向对象的文件流,但C风格函数在竞赛和底层编程中仍然常见,GESP四级也要求掌握。
4.1 文件打开与关闭:fopen / fclose
cpp
#include <cstdio>
FILE* fopen(const char* filename, const char* mode);
// 成功返回文件指针,失败返回 NULL
int fclose(FILE* stream);
// 关闭文件,成功返回0,失败返回 EOF文件打开模式速查表:
| 模式 | 含义 | 文件不存在时 | 文件已存在时 |
|---|---|---|---|
"r" | 只读(文本) | 打开失败,返回 NULL | 从头读取 |
"w" | 只写(文本) | 创建新文件 | 清空原有内容 |
"a" | 追加(文本) | 创建新文件 | 在末尾追加,保留原内容 |
"r+" | 读写(文本) | 打开失败 | 从头读写,不清空 |
"w+" | 读写(文本) | 创建新文件 | 清空原有内容 |
"a+" | 读+追加(文本) | 创建新文件 | 末尾追加,可读原内容 |
"rb" | 只读(二进制) | 同 "r" | 同 "r" |
"wb" | 只写(二进制) | 同 "w" | 同 "w" |
"ab" | 追加(二进制) | 同 "a" | 同 "a" |
🔥 高频考点:
"w"模式打开已有文件会清空原内容!"a"模式则保留原内容在末尾追加。考试常考"以下哪种模式不会覆盖文件原有内容"——答案是"a"或"r"。
打开与关闭的完整模板:
cpp
#include <cstdio>
int main() {
// 以只读模式打开文件
FILE* fp = fopen("data.txt", "r");
// 必须检查是否打开成功!
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
// ... 文件读写操作 ...
fclose(fp); // 用完必须关闭
return 0;
}4.2 格式化读写:fscanf / fprintf
这对函数的用法和 scanf/printf 几乎一样,只是多了第一个参数——文件指针。
cpp
int fscanf(FILE* stream, const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);完整示例:
cpp
#include <cstdio>
int main() {
// === 写入数据 ===
FILE* fout = fopen("scores.txt", "w");
if (fout == NULL) {
printf("无法创建文件!\n");
return 1;
}
// fprintf 用法与 printf 完全一致,只是输出到文件
fprintf(fout, "张三 85\n");
fprintf(fout, "李四 92\n");
fprintf(fout, "王五 78\n");
fclose(fout);
// === 读取数据 ===
FILE* fin = fopen("scores.txt", "r");
if (fin == NULL) {
printf("无法打开文件!\n");
return 1;
}
char name[20];
int score;
// fscanf 用法与 scanf 一致,只是从文件读取
while (fscanf(fin, "%s %d", name, &score) == 2) {
printf("%s: %d\n", name, score);
}
fclose(fin);
return 0;
}输出:
张三: 85
李四: 92
王五: 78⚠️ 易错点:
fscanf的返回值是成功读取的数据项数,不是读取的字符数。当到达文件末尾时返回EOF(通常为 -1)。上面用== 2判断是否成功读取了两个数据项(姓名和成绩),这是安全读取到文件末尾的推荐写法。
4.3 字符读写:fgetc / fputc
逐个字符读写文件,适合处理不确定格式或需要逐字符分析的场景。
cpp
int fgetc(FILE* stream); // 读取一个字符,返回ASCII码;到末尾返回 EOF
int fputc(int ch, FILE* stream); // 写入一个字符,成功返回该字符,失败返回 EOF完整示例:文件复制
cpp
#include <cstdio>
int main() {
FILE* fin = fopen("source.txt", "r");
FILE* fout = fopen("copy.txt", "w");
if (fin == NULL || fout == NULL) {
printf("文件打开失败!\n");
return 1;
}
int ch; // 注意用 int,不是 char!因为要容纳 EOF(-1)
while ((ch = fgetc(fin)) != EOF) {
fputc(ch, fout); // 逐字符复制
}
fclose(fin);
fclose(fout);
printf("文件复制完成!\n");
return 0;
}⚠️ 易错点:
fgetc的返回值类型是int而不是char!因为它需要返回EOF(值为 -1),而char可能是无符号类型(0~255),无法表示 -1。如果写成char ch; while ((ch = fgetc(fp)) != EOF),当读取到字节值为 255(0xFF)时会被误判为 EOF!
4.4 字符串读写:fgets / fputs
cpp
char* fgets(char* str, int num, FILE* stream);
// 最多读取 num-1 个字符,遇到换行符或EOF停止,自动加 '\0'
int fputs(const char* str, FILE* stream);
// 写入字符串到文件(不自动加换行)完整示例:逐行读取并添加行号
cpp
#include <cstdio>
#include <cstring>
int main() {
FILE* fin = fopen("article.txt", "r");
if (fin == NULL) {
printf("无法打开文件!\n");
return 1;
}
char line[256];
int lineNum = 0;
while (fgets(line, sizeof(line), fin) != NULL) {
lineNum++;
// fgets 会把换行符也读进来,可以去掉
int len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
}
printf("%3d: %s\n", lineNum, line);
}
fclose(fin);
return 0;
}⚠️ fgets 与 gets 的区别:
fgets要求指定最大读取长度num,更安全(防止缓冲区溢出);已被废弃的gets不限制长度,存在安全隐患。考试中可能出现"fgets的第二个参数含义"的选择题。
4.5 feof 判断文件末尾
cpp
int feof(FILE* stream);
// 到达文件末尾返回非0值,否则返回0feof 的工作机制与C++流的 eof() 类似——它在一次读取操作因到达末尾而失败后才返回真值。
cpp
// ❌ 不推荐的写法:可能导致最后一个数据被处理两次
int x;
while (!feof(fp)) {
fscanf(fp, "%d", &x);
printf("%d ", x); // 最后一个数可能被输出两次
}
// ✅ 推荐的写法:直接判断 fscanf 的返回值
int x;
while (fscanf(fp, "%d", &x) == 1) {
printf("%d ", x);
}
// ✅ 逐字符读取时判断 EOF
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}🔥 高频考点:为什么不推荐
while (!feof(fp))?因为feof()只有在读取操作失败后才被置位。循环条件为真时,可能已经到达末尾但尚未尝试读取,导致循环体多执行一次。记住:用读取函数的返回值判断,而不是feof!
五、C++风格文件流
C++ 在 <fstream> 头文件中提供了三个文件流类,使用 >> 和 << 运算符进行读写,语法与 cin/cout 高度一致。
5.1 三个核心类
| 类名 | 全称 | 功能 | 头文件 |
|---|---|---|---|
ifstream | input file stream | 读文件(输入) | <fstream> |
ofstream | output file stream | 写文件(输出) | <fstream> |
fstream | file stream | 读写文件 | <fstream> |
💡 记忆技巧:
i代表 input(输入/读取),o代表 output(输出/写入)。和cin(输入)、cout(输出)的方向一致。
5.2 ofstream:写文件
cpp
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 方式一:构造函数直接打开文件(推荐,更简洁)
ofstream fout("result.txt");
// 方式二:先创建对象,再调用 open()
// ofstream fout;
// fout.open("result.txt");
// 检查文件是否成功打开(必做!)
if (!fout.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}
// 使用 << 运算符写入,和 cout 用法完全一样
fout << "Hello, File!" << endl;
fout << 100 << " " << 200 << endl;
fout << "平均分:" << 95.5 << endl;
// 关闭文件(必做!)
fout.close();
return 0;
}result.txt 内容:
Hello, File!
100 200
平均分:95.55.3 ifstream:读文件
cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ifstream fin("data.txt");
if (!fin.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}
// 方式一:使用 >> 运算符读取(自动跳过空白字符)
int a, b;
fin >> a >> b;
cout << a << " + " << b << " = " << a + b << endl;
// 方式二:使用 getline 逐行读取
fin.ignore(); // 跳过 >> 残留的换行符
string line;
while (getline(fin, line)) {
cout << "读到一行:" << line << endl;
}
fin.close();
return 0;
}⚠️ 易错点:
>>运算符会自动跳过空格、换行等空白字符,但读取后换行符会留在输入缓冲区。如果紧接着使用getline,会读到一个空行。解决方法是在getline前调用fin.ignore()跳过残留换行符。
5.4 fstream:同时读写
fstream 可以同时进行读和写操作,需要指定 ios::in | ios::out 模式。
cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 以读写模式打开文件
fstream file("data.txt", ios::in | ios::out);
if (!file.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}
// 写入数据
file << "第一行数据" << endl;
file << "第二行数据" << endl;
// 写入后文件指针在末尾,需要移回开头才能读取
file.seekg(0, ios::beg); // seekg = seek get,移动读指针
// 读取数据
string line;
while (getline(file, line)) {
cout << line << endl;
}
file.close();
return 0;
}💡 了解即可:
fstream同时读写需要移动文件指针(seekg/seekp),操作较复杂,GESP四级考试中很少涉及,重点掌握ofstream和ifstream即可。
5.5 文件打开模式标志
C++ 文件流通过 ios 命名空间中的常量指定打开模式:
| 模式标志 | 含义 | 说明 |
|---|---|---|
ios::in | 读模式 | 打开文件用于读取(ifstream 默认) |
ios::out | 写模式 | 表示"可写入",单独使用时不一定清空文件;ofstream 默认会加上 ios::trunc |
ios::app | 追加模式 | 写入内容追加到文件末尾,不覆盖原内容 |
ios::trunc | 覆盖模式 | 打开时清空文件原有内容(ofstream 默认行为) |
ios::ate | 定位末尾 | 打开后文件指针定位到末尾 |
ios::binary | 二进制模式 | 以二进制方式打开文件 |
模式可以组合使用,用 |(按位或)连接:
cpp
// 以读写模式打开
fstream file("data.txt", ios::in | ios::out);
// 以追加模式写入(保留原内容)
ofstream fout("log.txt", ios::app);
// 以二进制模式读取
ifstream fin("image.dat", ios::in | ios::binary);🔥 高频考点:
ios::app(追加)与ios::trunc(覆盖)的区别是常考判断题!
ofstream默认使用ios::out | ios::trunc,即覆盖写入——打开时清空原内容!- 如果不想覆盖,必须显式指定
ios::app。
5.6 eof() 判断与安全读取
与C风格的 feof 类似,C++流的 eof() 也有"滞后性"问题。
cpp
// ❌ 不推荐:可能导致最后一个数据重复处理
int x;
while (!fin.eof()) {
fin >> x;
cout << x << " "; // 最后一个值可能输出两次
}
// ✅ 推荐:直接将读取表达式作为循环条件
int x;
while (fin >> x) {
cout << x << " ";
}
// ✅ 逐字符读取
char ch;
while (fin.get(ch)) {
cout << ch;
}🔥 高频考点:为什么不推荐
while (!fin.eof())?
eof()在一次读取操作因到达文件末尾而失败后才返回true。假设文件中只有一个数字42:
- 第1次循环:
!fin.eof()为true,fin >> x成功读到 42- 第2次循环:
!fin.eof()仍为true(尚未尝试过末尾读取),fin >> x失败,x保持为 42eof()终于变为true,循环结束结果 42 被输出了两次!永远使用
while (fin >> x)的写法。
六、C风格 vs C++风格 对比
| 对比项 | C风格(FILE*) | C++风格(fstream) |
|---|---|---|
| 头文件 | <cstdio> | <fstream> |
| 核心对象 | FILE* 文件指针 | ifstream/ofstream/fstream 对象 |
| 打开文件 | fopen("f.txt", "r") | ifstream fin("f.txt") |
| 关闭文件 | fclose(fp) | fin.close() |
| 格式化读 | fscanf(fp, "%d", &x) | fin >> x |
| 格式化写 | fprintf(fp, "%d", x) | fout << x |
| 字符读 | fgetc(fp) | fin.get(ch) |
| 字符写 | fputc(ch, fp) | fout.put(ch) |
| 判断末尾 | fscanf 返回值 / fgetc 返回 EOF | fin >> x 返回值 / fin.get(ch) |
| 检查打开成功 | fp == NULL | !fin.is_open() |
| 打开模式 | 字符串 "r"/"w"/"a" | 常量 ios::in/ios::out/ios::app |
| 竞赛适用场景 | 与 freopen 配合,底层操作 | 面向对象,多文件操作更方便 |
💡 备考建议:两种风格都要求掌握。选择题常考语法辨析(如参数类型、返回值判断),编程题推荐使用更简洁的
freopen或fstream。
七、文件操作常见易错点
易错点1:不检查文件是否打开成功
cpp
// ❌ 错误:文件不存在时后续操作全部失败,变量为随机值
ifstream fin("nonexistent.txt");
int x;
fin >> x; // 读取失败
cout << x; // 输出未初始化的垃圾值
// ✅ 正确:打开后立即检查
ifstream fin("nonexistent.txt");
if (!fin.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}🔥 文件打开失败的常见原因:文件不存在(读模式)、路径错误、权限不足、文件被其他程序占用。
易错点2:忘记关闭文件
cpp
// ❌ 错误:写入的数据可能还在缓冲区,未真正写入磁盘
ofstream fout("data.txt");
fout << "重要数据";
// 没有 close(),程序异常退出时数据可能丢失
// ✅ 正确
ofstream fout("data.txt");
fout << "重要数据";
fout.close(); // 刷新缓冲区,确保数据落盘⚠️ 为什么必须关闭文件?
- 写入的数据可能还留在缓冲区中,
close()会强制刷新,确保数据写入磁盘- 操作系统对同时打开的文件数量有限制
- 显式关闭是最佳实践,考试中忘记
close()可能被扣分
易错点3:覆盖 vs 追加
cpp
// ofstream 默认覆盖模式,会清空原有内容!
ofstream fout1("log.txt"); // 等价 ios::out | ios::trunc
fout1 << "新内容"; // 原有内容全部丢失!
// 追加模式:保留原有内容,新内容写在末尾
ofstream fout2("log.txt", ios::app);
fout2 << "追加的内容";易错点4:>> 与 getline 混用
cpp
// ❌ 问题代码
int n;
string line;
fin >> n; // 读取整数后,换行符留在缓冲区
getline(fin, line); // 读到残留换行符,line 为空字符串!
// ✅ 解决方案:中间加 ignore()
int n;
string line;
fin >> n;
fin.ignore(); // 跳过残留的换行符
getline(fin, line);易错点5:Windows 路径中的反斜杠
cpp
// ❌ 错误:单反斜杠是转义字符
ifstream fin("C:\Users\test\data.txt"); // 编译错误或路径错误!
// ✅ 正确:双反斜杠转义,或直接用正斜杠
ifstream fin1("C:\\Users\\test\\data.txt");
ifstream fin2("C:/Users/test/data.txt"); // C++ 也能识别正斜杠易错点速查表
| 易错点 | 后果 | 正确做法 |
|---|---|---|
不检查 is_open() / NULL | 打开失败时产生未定义行为 | 打开后立即检查 |
不调用 close() / fclose() | 数据可能未写入磁盘 | 用完后立即关闭 |
用 while(!eof()) / while(!feof()) | 最后一个数据可能重复读取 | 用读取函数的返回值判断 |
>> 后直接 getline | 读到空行 | 中间加 ignore() |
| 默认构造覆盖文件 | 原有数据丢失 | 追加用 ios::app 或 "a" |
| Windows 路径用单反斜杠 | 编译错误或路径错误 | 用 \\ 或 / |
fgetc 返回值用 char 接收 | 字节 0xFF 被误判为 EOF | 用 int 接收 |
八、综合实战:学生成绩管理
下面通过一个综合示例,串联文件读写的完整流程。
cpp
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
using namespace std;
int main() {
// ========== 第一步:写入成绩数据 ==========
ofstream fout("scores.txt");
if (!fout.is_open()) {
cout << "无法创建文件!" << endl;
return 1;
}
// 写入5名学生的姓名和成绩
fout << "张三 85" << endl;
fout << "李四 92" << endl;
fout << "王五 78" << endl;
fout << "赵六 95" << endl;
fout << "钱七 88" << endl;
fout.close();
// ========== 第二步:读取并统计 ==========
ifstream fin("scores.txt");
if (!fin.is_open()) {
cout << "无法打开文件!" << endl;
return 1;
}
string name;
int score;
int sum = 0, count = 0;
int maxScore = -1, minScore = 101;
string maxName, minName;
// 使用 while (fin >> ...) 安全读取到文件末尾
while (fin >> name >> score) {
cout << name << ": " << score << endl;
sum += score;
count++;
if (score > maxScore) {
maxScore = score;
maxName = name;
}
if (score < minScore) {
minScore = score;
minName = name;
}
}
fin.close();
// ========== 第三步:输出统计结果 ==========
cout << "----------" << endl;
cout << fixed << setprecision(1);
cout << "平均分:" << (double)sum / count << endl;
cout << "最高分:" << maxName << " " << maxScore << endl;
cout << "最低分:" << minName << " " << minScore << endl;
// ========== 第四步:将统计结果追加写入文件 ==========
ofstream foutApp("scores.txt", ios::app); // 追加模式
if (!foutApp.is_open()) {
cout << "无法打开文件进行追加!" << endl;
return 1;
}
foutApp << "----------" << endl;
foutApp << "平均分:" << fixed << setprecision(1)
<< (double)sum / count << endl;
foutApp << "最高分:" << maxName << " " << maxScore << endl;
foutApp << "最低分:" << minName << " " << minScore << endl;
foutApp.close();
return 0;
}屏幕输出:
张三: 85
李四: 92
王五: 78
赵六: 95
钱七: 88
----------
平均分:87.6
最高分:赵六 95
最低分:王五 78💡 代码要点:① 写入用默认模式(覆盖),追加统计结果用
ios::app;② 读取用while (fin >> name >> score)安全判断末尾;③ 每次打开文件后都检查is_open();④ 用完立即close()。
九、📝 实战练习
练习题 1(编程题)
题目: 编写程序,将 1 到 N(N 从键盘输入)的所有偶数写入文件 even.txt,每行一个偶数;然后再从该文件中读取所有偶数,计算它们的和并输出到屏幕。
🔑 点击查看答案与解析
参考代码:
cpp
#include <iostream>
#include <fstream>
using namespace std;
int main() {
int n;
cin >> n;
// 第一步:写入偶数
ofstream fout("even.txt");
if (!fout.is_open()) {
cout << "文件创建失败!" << endl;
return 1;
}
for (int i = 2; i <= n; i += 2) {
fout << i << endl;
}
fout.close();
// 第二步:读取并求和
ifstream fin("even.txt");
if (!fin.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}
int num, sum = 0;
while (fin >> num) {
sum += num;
}
fin.close();
cout << "偶数之和:" << sum << endl;
return 0;
}输入:
10输出:
偶数之和:30解析:
- 第一步用
ofstream创建/覆盖文件,将 2, 4, 6, 8, 10 逐行写入 - 第二步用
ifstream读取,while (fin >> num)安全判断文件末尾 - 求和:2 + 4 + 6 + 8 + 10 = 30
- 注意每次文件操作完毕后都调用
close(),先关闭写流再打开读流
练习题 2(编程题)
题目: 文件 input.txt 中存放了 n 个学生的姓名和成绩(第一行为 n,后续 n 行每行一个姓名和一个整数成绩)。要求使用 freopen 重定向,读取数据后计算平均分,并将成绩高于平均分的学生姓名和成绩输出到文件 output.txt。
input.txt 内容:
5
张三 85
李四 92
王五 78
赵六 95
钱七 88🔑 点击查看答案与解析
参考代码:
cpp
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
int main() {
// 文件重定向:之后的 cin 从 input.txt 读,cout 写到 output.txt
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int n;
cin >> n;
string names[1005];
int scores[1005];
int sum = 0;
for (int i = 0; i < n; i++) {
cin >> names[i] >> scores[i];
sum += scores[i];
}
double avg = (double)sum / n;
cout << "平均分:" << avg << endl;
cout << "高于平均分的学生:" << endl;
for (int i = 0; i < n; i++) {
if (scores[i] > avg) {
cout << names[i] << " " << scores[i] << endl;
}
}
return 0;
}output.txt 内容:
平均分:87.6
高于平均分的学生:
李四 92
赵六 95
钱七 88解析:
- 使用
freopen将stdin重定向到input.txt,stdout重定向到output.txt - 代码主体与普通键盘输入/屏幕输出完全一样,无需修改
cin/cout - 平均分使用
(double)sum / n进行浮点数除法,避免整数除法截断 - 高于 87.6 分的有李四(92)、赵六(95)、钱七(88)
⚠️ 注意:使用
freopen时,数组大小要足够(这里开 1005),防止数据量较大时越界。
练习题 3(编程题)
题目: 使用C风格文件操作(fopen/fgetc/fclose),统计文件 article.txt 中大写字母、小写字母、数字字符和其他字符的个数,并将统计结果输出到屏幕。
🔑 点击查看答案与解析
参考代码:
cpp
#include <cstdio>
#include <cctype> // isupper, islower, isdigit
int main() {
FILE* fp = fopen("article.txt", "r");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
int upper = 0, lower = 0, digit = 0, other = 0;
int ch; // 必须用 int,因为要容纳 EOF(-1)
while ((ch = fgetc(fp)) != EOF) {
if (isupper(ch)) upper++;
else if (islower(ch)) lower++;
else if (isdigit(ch)) digit++;
else other++;
}
fclose(fp);
printf("大写字母:%d\n", upper);
printf("小写字母:%d\n", lower);
printf("数字字符:%d\n", digit);
printf("其他字符:%d\n", other);
return 0;
}假设 article.txt 内容为:
Hello 2024!
GESP C++ Level-4.输出:
大写字母:7
小写字母:8
数字字符:5
其他字符:10解析:
- 使用
fopen("article.txt", "r")以只读文本模式打开文件 - 检查
fp == NULL判断是否打开成功 fgetc逐个字符读取,返回值用int类型接收(容纳 EOF)- 使用
<cctype>中的isupper/islower/isdigit宏分类统计 - 统计完毕后
fclose(fp)关闭文件
⚠️ 易错点提醒:本题故意使用C风格操作,考试中如果题目明确要求
FILE*或fopen,就不能用ifstream。注意fgetc返回值必须用int!
练习题 4(单选题)
题目: 以下关于文件打开模式的说法,错误的是( ) A. "r" 模式打开文件用于读取,文件必须存在
B. "w" 模式打开文件用于写入,文件不存在则创建,存在则覆盖
C. "a" 模式打开文件用于追加,新内容写在文件末尾
D. "r+" 模式打开文件用于读写,文件不存在时自动创建
🔑 点击查看答案与解析
答案: D
解析:
- A 正确 ✅:
"r"= read,只读模式,文件必须存在,否则打开失败 - B 正确 ✅:
"w"= write,只写模式,会清空原有内容(覆盖) - C 正确 ✅:
"a"= append,追加模式,新内容写在末尾 - D 错误 ❌:
"r+"是读写模式,但文件必须存在,不存在时不会自动创建("w+"才会创建)
💡 记忆口诀:r 是读(必须存在),w 是写(清空重来),a 是追加(末尾添加)。带 + 号就是读写双模式。
练习题 5(判断题)
题目: 使用 freopen("data.txt", "r", stdin); 后,cin 会从文件 data.txt 读取数据,而不是从键盘读取。( )
🔑 点击查看答案与解析
答案: 正确 ✅
解析: freopen 的作用是重定向标准流。freopen("data.txt", "r", stdin) 把标准输入流 stdin 重定向到文件,之后所有从 stdin 读取的操作(包括 cin、scanf、getchar 等)都会从文件读取。
🍎 生活比喻:就像把自来水管从"市政供水"切换到"自家水箱",打开水龙头流出来的就是水箱里的水了。竞赛中常用 freopen 避免手动输入大量测试数据。
练习题 6(单选题)
题目: 以下哪种方式是读取文件的正确写法?( ) A. while (fin.eof()) { fin >> x; ... }
B. while (!fin.eof()) { fin >> x; ... }
C. while (fin >> x) { ... }
D. for (int i = 0; i < 100; i++) { fin >> x; ... }
🔑 点击查看答案与解析
答案: C
解析:
- A 错误:eof() 为 true 时表示已经到末尾了,应该是 !eof()
- B 错误:
!eof()的写法有陷阱——读完最后一个数据后,eof() 还没变成 true,会多读一次无效数据 - C 正确 ✅:
fin >> x的返回值就是 fin 对象,它可以被转换为 bool 类型。读取成功为 true,失败(到末尾)为 false。这是最安全的写法 - D 错误:硬编码循环次数容易读多或读少
🔥 高频考点:考试中常考 eof() 的错误用法。记住:永远用
while (fin >> x)的写法,不要用 eof() 判断!
练习题 7(多选题)
题目: 以下说法正确的有( )
① 文件操作完成后应该调用 close() 关闭文件
② ofstream 默认是追加模式,不会覆盖原有内容
③ ifstream 打开不存在的文件时,is_open() 返回 false
④ fgetc 的返回值应该用 char 类型接收
⑤ freopen 的第三个参数是要重定向的流(stdin/stdout/stderr) A. ①②③
B. ①③⑤
C. ②③④
D. ②④⑤
🔑 点击查看答案与解析
答案: B
解析:
- ① 正确 ✅:关闭文件可以刷新缓冲区,确保数据写入磁盘
- ② 错误 ❌:ofstream 默认是覆盖模式(ios::trunc),不是追加
- ③ 正确 ✅:文件不存在时打开失败,is_open() 返回 false
- ④ 错误 ❌:fgetc 返回值应该用
int类型接收,因为要容纳 EOF(值为 -1,超出 char 范围) - ⑤ 正确 ✅:freopen 的第三个参数是流,常用 stdin/stdout
选 B(①③⑤)。
练习题 8(编程题)
题目: 文件 nums.txt 中存储了若干个整数(用空格分隔)。请编写程序:
- 从文件中读取所有整数
- 找出其中的最大值和最小值
- 将结果写入文件
result.txt,格式:最大值=xx,最小值=xx
🔑 点击查看答案与解析
参考答案:
cpp
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream fin("nums.txt");
if (!fin.is_open()) {
cout << "无法打开输入文件!" << endl;
return 1;
}
int x;
int maxVal, minVal;
bool first = true;
while (fin >> x) {
if (first) {
maxVal = minVal = x;
first = false;
} else {
if (x > maxVal) maxVal = x;
if (x < minVal) minVal = x;
}
}
fin.close();
ofstream fout("result.txt");
if (!fout.is_open()) {
cout << "无法创建输出文件!" << endl;
return 1;
}
fout << "最大值=" << maxVal << endl;
fout << "最小值=" << minVal << endl;
fout.close();
return 0;
}解析:
这是文件操作的经典题型,核心步骤:
- 用 ifstream 打开输入文件,检查是否成功
- 用
while (fin >> x)循环读取所有整数 - 遍历过程中记录最大值和最小值
- 用 ofstream 打开输出文件,写入结果
- 用完文件后及时 close()
💡 小技巧:第一个数既是最大值也是最小值,后面每个数都和当前最大/最小值比较更新。
十、📊 考点总结表
| 考点 | 重要程度 | 出题形式 | 易错点 |
|---|---|---|---|
| 文件打开模式(r/w/a) | ⭐⭐⭐⭐ | 选择/判断 | 各种模式的区别记混 |
| freopen 重定向 | ⭐⭐⭐⭐ | 编程题/代码填空 | 三参数的顺序和含义 |
| ifstream 读文件 | ⭐⭐⭐⭐ | 编程题/代码阅读 | 忘记检查 is_open() |
| ofstream 写文件 | ⭐⭐⭐⭐ | 编程题 | 默认覆盖,不是追加 |
| close() 关闭文件 | ⭐⭐⭐⭐ | 选择/判断 | 忘记关闭导致数据丢失 |
| eof() 的正确用法 | ⭐⭐⭐⭐⭐ | 选择/代码阅读 | 用 eof() 判断会多读一次 |
| 覆盖 vs 追加 | ⭐⭐⭐⭐ | 选择/判断 | 默认是覆盖(trunc),追加需ios::app |
| is_open() 检查 | ⭐⭐⭐⭐⭐ | 选择/判断/编程 | 打开失败仍继续操作 |
| C风格 fopen/fclose | ⭐⭐⭐ | 选择/代码阅读 | NULL 判断、fgetc用int接收 |
| >> 与 getline 混用 | ⭐⭐⭐ | 选择/代码阅读 | 残留换行符导致读空行 |
⭐ 必背速记卡
┌─────────────────────────────────────────────┐
│ 文件操作 · 必背速记卡 │
├─────────────────────────────────────────────┤
│ 1. 文件用于持久化存储,程序结束数据不丢失 │
│ 2. freopen三参数:文件名、模式、流 │
│ 3. "r"只读(必须存在),"w"只写(覆盖),"a"追加│
│ 4. 二进制加b:"rb"读二进制,"wb"写二进制 │
│ 5. ifstream 读文件,ofstream 写文件 │
│ 6. 打开后检查 is_open(),失败要处理 │
│ 7. 用完文件 close(),刷新缓冲区保数据 │
│ 8. 读文件用 while (fin >> x),别用 eof() │
│ 9. eof() 陷阱:读完最后一个还没到eof状态 │
│ 10. ofstream 默认覆盖,追加用 ios::app │
│ 11. C风格:fopen打开,fclose关闭,FILE*指针 │
│ 12. fgetc 返回值用 int 接收,容纳 EOF(-1) │
│ 13. >> 和 getline 混用:中间加 ignore() │
│ 14. Windows路径反斜杠要转义:\\ 才表示一个\ │
│ 15. 竞赛推荐 freopen:最简单,代码改动最小 │
└─────────────────────────────────────────────┘