【GESP】C++四级考试大纲知识点梳理, (10) 文件读写和重定向
孩子放假,出游一周,没有更新。刚刚归来,继续学习。
GESP C++四级官方考试大纲中,共有11条考点,本文针对第10条考点进行分析介绍。
(10)掌握文件操作中的重定向,实现文件读写操作,了解文本文件的分类,掌握写操作、读操作、读写操作。
其实,由于本人也是边学、边实验、边总结,且对考纲深度和广度的把握属于个人理解。因此本文更多的不是一个教程,而是个人知识梳理,如有遗漏、疏忽,欢迎指正、交流。
四级其他考点回顾:
一、文件操作中的重定向
重定向(Redirection)通常指将标准输入/输出(如cin
/cout
)切换到文件或其他流。
这句话的意思可以分解为两个关键部分来理解:即:
- 什么是“标准输入/输出”
- 什么是“重定向”
1.1 什么是“标准输入/输出”?
“标准输入/输出”(Standard Input/Output,简称 Standard I/O)是指在程序运行时,操作系统默认提供的一组输入与输出通道,用于与用户或其他程序进行交互。
在 C/C++ 中,这种机制提供了一个抽象的设备接口,可以简化 I/O 操作,不需要开发者直接与底层硬件或文件系统打交道。
1.1.1 标准输入/输出的组成
名称 | 标识符 | 默认连接的设备 | 用途 |
---|---|---|---|
标准输入 | stdin | 键盘 | 接收输入 |
标准输出 | stdout | 显示器(终端) | 输出结果 |
标准错误 | stderr | 显示器(终端) | 输出错误或日志 |
这些是由操作系统在程序启动时自动打开的三个“文件流”。
1.1.2 在C中的实现方式
- 使用
<stdio.h>
中的全局流指针:
1
2
3
4
5
6
int main() {
int x;
scanf("%d", &x); // 从 stdin 读取
printf("x = %d\n", x); // 向 stdout 输出
fprintf(stderr, "error\n"); // 向 stderr 输出
}
1.1.3 在C++中的实现方式
- 使用
<iostream>
中的全局对象:
1
2
3
4
5
6
7
8
#include <iostream>
int main() {
int x;
std::cin >> x; // 从标准输入stdin读取
std::cout << "x = " << x; // 向标准输出stdout输出
std::cerr << "error\n"; // 向标准错误stderr输出
}
1.1.4 为什么要使用“标准”输入输出?
✅ 1. 统一接口,抽象底层
- 不管是从键盘输入,还是从文件中读取,都可以抽象为“输入流”
- 便于程序移植,平台无关
✅ 2. 简化操作
- 无需用户手动打开/关闭输入输出设备
- 操作系统启动程序时自动创建和管理
✅ 3. 支持重定向
- 例如:用户可以在命令行中重定向输入/输出:
1
./a.out < input.txt > output.txt
此时:
stdin
→input.txt
stdout
→output.txt
1.2 什么是“重定向”?
重定向(Redirection) 的意思是:把标准输入输出“指向”的对象从默认设备(如键盘、屏幕)切换为“文件”或“其他流”。
举个例子来理解,默认情况下:
1
2
3
int x;
cin >> x; // 从键盘读取
cout << x; // 输出到屏幕
使用重定向后,让 cin/cout 不再连着键盘/屏幕,而是连到“文件”:
重定向的本质是什么?
重定向只是改变了数据的”流向”,就像把水管接到了不同的水源和出水口
- 默认状态:
1 [键盘] ——(cin)——> 程序 ——(cout)——> [屏幕]
- 重定向后:
1 [文件input.txt] ——(cin)——> 程序 ——(cout)——> [文件output.txt]
cin
依然是输入流(istream
类型),cout
依然是输出流(ostream
类型)- 重定向不会改变它们的本质功能和使用方式,只是改变了数据来源和去向
1.2.1 重定向常用场景
场景 | 原因/好处 |
---|---|
读写大批量测试数据 | 避免手工输入,提高效率 |
自动化测试 | 输入输出由文件控制,可重复测试 |
竞赛/OJ平台 | 程序需要从特定文件中读写数据 |
日志输出 | 将 cout 重定向为日志文件 |
附:重定在信奥竞赛中的应用,信奥做题文件输入输出说明(CSP开始做题的方法)
二、C/C++中重定向的方法
在C/C++中,输入输出重定向是将程序的标准输入(stdin
)、标准输出(stdout
)或标准错误(stderr
)从默认的键盘/终端重定向到文件或其他设备的过程。C++中实现重定向主要有以下几种方式:
2.1 使用 freopen
函数
C标准库提供的 freopen
函数可以在程序运行时将标准输入输出流重定向到文件。
函数原型:
1
FILE* freopen(const char* filename, const char* mode, FILE* stream);
- 常用方式:
- 重定向标准输入:
freopen("input.txt", "r", stdin);
- 重定向标准输出:
freopen("output.txt", "w", stdout);
- 重定向标准错误:
freopen("error.txt", "w", stderr);
- 重定向标准输入:
- 优点:
- 在程序内部实现重定向,灵活性高,可动态指定文件。
- 跨平台,适用于大多数C/C++环境。
- 缺点:
- 一旦重定向,标准输入输出流全局改变,无法方便地在程序中切换回默认终端。
- 不适合需要频繁切换输入输出源的场景。
- 适用场景:
- 需要在程序内部指定输入输出文件的场景。
- 竞赛编程(如ACM、OJ、CSP)中常用于从文件读取测试数据。
- 需要将输出保存到文件的固定场景。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
// 重定向标准输入到 input.txt
freopen("input.txt", "r", stdin);
// 重定向标准输出到 output.txt
freopen("output.txt", "w", stdout);
int x;
while (cin >> x) {
cout << x * 2 << endl;
}
return 0;
}
运行效果:
- 输入文件
input.txt
:1 2 3
- 输出文件
output.txt
:2 4 6
2.2 使用文件流(C++ fstream
)
C++的 <fstream>
库提供 ifstream
和 ofstream
类,用于直接操作文件流,而非依赖标准输入输出。
- 使用
ifstream
读取文件,代替cin
。 使用
ofstream
写入文件,代替cout
。- 优点:
- 完全在程序内部控制,灵活性高,可同时操作多个文件。
- 支持动态打开/关闭文件流,不影响标准输入输出。
- 提供更细粒度的文件操作(如随机访问、格式化)。
- 缺点:
- 代码复杂度略高于
freopen
或命令行重定向。 - 需要显式管理文件的打开和关闭。
- 代码复杂度略高于
- 适用场景:
- 需要同时处理多个输入输出文件。
- 需要精细控制文件操作(如追加、格式化输出)。
- 不希望影响标准输入输出的场景。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream inFile("input.txt"); // 打开输入文件
ofstream outFile("output.txt"); // 打开输出文件
if (!inFile || !outFile) {
cerr << "Error opening file" << endl;
return 1;
}
int x;
while (inFile >> x) {
outFile << x * 2 << endl;
}
inFile.close();
outFile.close();
return 0;
}
运行效果:
- 输入文件
input.txt
:1 2 3
- 输出文件
output.txt
:2 4 6
2.3 使用系统调用(dup
和 dup2
)
在类Unix系统中(如Linux),使用低级文件描述符操作(如 dup
和 dup2
)实现重定向。这是操作系统级别的重定向,适用于需要底层控制的场景。
- 使用
open
打开文件,获取文件描述符。 使用
dup2
将标准输入(0)、标准输出(1)或标准错误(2)的文件描述符替换为目标文件描述符。- 优点:
- 提供底层控制,适合需要精细管理文件描述符的场景。
- 可与管道、套接字等结合使用。
- 缺点:
- 平台相关,仅适用于类Unix系统(如Linux、macOS)。
- 代码复杂,调试困难,容易出错。
- 不如高级方法(如
fstream
)直观。
- 适用场景:
- 需要与操作系统其他功能(如管道、进程间通信)结合的场景。
- 系统编程或需要低级控制的场景。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main() {
// 打开输入文件
int in_fd = open("input.txt", O_RDONLY);
if (in_fd == -1) {
perror("Error opening input file");
return 1;
}
// 打开输出文件
int out_fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (out_fd == -1) {
perror("Error opening output file");
return 1;
}
// 重定向标准输入和输出
dup2(in_fd, STDIN_FILENO);
dup2(out_fd, STDOUT_FILENO);
// 关闭文件描述符
close(in_fd);
close(out_fd);
int x;
while (cin >> x) {
cout << x * 2 << endl;
}
return 0;
}
运行效果:
- 输入文件
input.txt
:1 2 3
- 输出文件
output.txt
:2 4 6
2.4 使用 C++ std::cin, std::cout 的 rdbuf 重定向(C++风格)
使用 C++ 的 rdbuf
方法进行重定向是一种更加 C++ 风格的方案。这种方式的特点如下:
- 优点:
- 完全符合 C++ 的面向对象设计理念
- 可以方便地保存和恢复原有的缓冲区
- 提供更细粒度的控制
- 异常安全
- 缺点:
- 代码相对复杂一些
- 需要理解流缓冲区的概念
- 适用场景:
- 需要临时重定向后还原的场景
- 面向对象的 C++ 工程项目
- 需要异常安全保证的场景
这种方式通过直接操作流的缓冲区(buffer)来实现重定向,是一种更加底层和灵活的方案。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <fstream>
int main() {
std::ifstream inFile("input.txt");
std::ofstream outFile("output.txt");
// 备份原来的缓冲区指针
std::streambuf* cinBackup = std::cin.rdbuf();
std::streambuf* coutBackup = std::cout.rdbuf();
// 重定向
std::cin.rdbuf(inFile.rdbuf());
std::cout.rdbuf(outFile.rdbuf());
int x;
while (std::cin >> x) {
std::cout << "Read: " << x << std::endl;
}
// 恢复原来的缓冲区
std::cin.rdbuf(cinBackup);
std::cout.rdbuf(coutBackup);
return 0;
}
2.5 命令行重定向
通过操作系统的命令行重定向符号(>
、>>
、<
等)实现输入输出重定向。这是外部重定向方式,无需修改程序代码。
- 输入重定向:使用
<
将文件内容作为程序的输入。- 示例:
./program < input.txt
- 示例:
- 输出重定向:
>
:覆盖目标文件。- 示例:
./program > output.txt
- 示例:
>>
:追加到目标文件。- 示例:
./program >> output.txt
- 示例:
- 输入输出同时重定向:
- 示例:
./program < input.txt > output.txt
- 示例:
- 优点:
- 无需修改程序代码,简单易用。
- 由操作系统处理,适用于快速测试或临时重定向。
- 缺点:
- 依赖命令行环境,无法在程序内部动态控制重定向。
- 不适合复杂场景(如动态选择文件或管道)。
- 适用场景:
- 快速测试程序输入输出。
- 批处理脚本中处理文件输入输出。
- 不需要程序内部控制重定向逻辑的场景。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
int main() {
int x;
// 从标准输入读取
while (cin >> x) {
// 输出到标准输出
cout << x * 2 << endl;
}
return 0;
}
运行示例:
- 输入文件
input.txt
内容:1 2 3
- 运行:
./program < input.txt > output.txt
- 输出文件
output.txt
内容:2 4 6
对比总结
方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
freopen | 程序内控制,跨平台,简单易用 | 全局修改流,难以切换 | 竞赛编程、固定文件输入输出 |
C++ fstream | 灵活,支持多文件操作,不影响标准流 | 代码稍复杂,需管理文件 | 复杂文件操作、动态文件处理 |
系统调用(dup2 ) | 底层控制,适合系统编程 | 平台相关,代码复杂 | 系统编程、管道或套接字结合 |
C++ rdbuf | 面向对象,异常安全,支持流缓冲 | 代码复杂,需理解流缓冲 | 临时重定向、面向对象编程 |
命令行重定向 | 简单,无需改代码,跨平台 | 依赖命令行,无法动态控制 | 快速测试、批处理脚本 |
所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code
GESP各级别考纲、真题讲解、知识拓展和练习清单等详见【置顶】【GESP】C++ 认证学习资源汇总
“luogu-”系列题目已加入洛谷Java、C++初学团队,作业清单,可在线评测,团队名额有限。
“bcqm-”系列题目可在编程启蒙题库进行在线评测。
欢迎加入:Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答
欢迎加入:C++ GESP/CSP认证学习QQ频道,考试资源总结汇总
欢迎加入:C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助