OI/ICPC 竞赛文件读写实战指南:freopen、fopen 与 C++ 文件流(基于 OI-wiki)
【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki
导读
在 OI/ICPC 竞赛中,提交的评测机通常通过标准输入输出进行评测,但本机调试、对拍和自带样例测试时几乎必然涉及文件读写——无论是从data.in读入数据,还是把结果写到data.out,都是每个选手必须掌握的技能。本文以 OI-wiki 的 文件操作文档 为主体,系统讲解 C/C++ 中三类文件操作方案:重定向流的标准做法freopen、面向文件指针的fopen、以及 C++ 风格的ifstream/ofstream文件流,并补充竞赛场景下的注意事项与仓库源码佐证。读完本文,你将能根据场景熟练选择文件操作方式,并避开常见的文件读写陷阱。
文件的概念与 C/C++ 的看待方式
在操作系统中,文件是根据特定目的而收集在一起的有关数据的集合。C/C++ 把每一个文件都看成是一个有序的字节流,每个文件都以**文件结束标志(EOF)**结束。如果要操作某个文件,程序应该首先打开该文件;每当一个文件被打开后,该文件就和一个流关联起来,这里的流实际上是一个字节序列。
C/C++ 将文件分为两大类:
- 文本文件:简单的文本文件,例如
.in、.out、.txt数据文件,是竞赛中最常打交道的类型; - 二进制文件:特殊格式的文件或者可执行代码文件等,如
.exe、.dat。
竞赛数据文件几乎都是文本文件,因此掌握文本文件的读写即可应对绝大多数场景。
文件操作的三个步骤
无论使用哪种 API,文件操作的流程都是固定的三步:
- 打开文件:将文件指针指向文件,决定打开文件类型(读 / 写 / 追加);
- 读、写操作:对文件进行读、写操作(比赛中主要用到这两类操作);
- 关闭文件:在使用完文件后,关闭文件。
需要特别强调的是:打开的文件请记得关闭。未关闭的文件指针会在进程中积累,造成资源泄漏。仓库中 常见错误 一节也专门指出:对拍时未关闭文件指针fclose(fp)就又令fp = fopen(),会使得进程出现大量的文件野指针。
freopen函数:最常用的竞赛文件重定向方案
函数简介
freopen用于将指定输入输出流以指定方式重定向到文件,包含于头文件stdio.h(C++ 中为cstdio)中。它的最大优势是:在不改变代码原貌的情况下改变输入输出环境——重定向之后,scanf/printf/cin/cout等原本读写stdin/stdout的函数会自动改读写到目标文件,程序主体逻辑完全无需修改。
使用freopen时应当保证流是可靠的,即目标文件确实存在(读模式)或可创建(写模式)。
函数原型与参数
FILE* freopen(const char* filename, const char* mode, FILE* stream);| 参数 | 含义 |
|---|---|
filename | 要打开的文件名 |
mode | 文件打开的模式,表示文件访问的权限 |
stream | 文件指针,通常使用标准文件流(stdin/stdout)或标准错误输出流(stderr) |
| 返回值 | 文件指针,指向被打开文件 |
stream参数通常传stdin(重定向输入)、stdout(重定向输出)或stderr(重定向错误输出),这也是该函数与fopen的本质区别:freopen不返回新流,而是替换已有标准流的目标。
文件打开模式(选读)
mode决定文件访问权限,常用模式如下:
| 模式 | 含义 |
|---|---|
r | 以只读方式打开文件,文件必须存在,只允许读入数据(常用) |
r+ | 以读/写方式打开文件,文件必须存在,允许读/写数据 |
rb | 以只读方式打开二进制文件,文件必须存在,只允许读入数据 |
rb+ | 以读/写方式打开二进制文件,文件必须存在,允许读/写数据 |
rt+ | 以读/写方式打开文本文件,允许读/写数据 |
w | 以只写方式打开文件,文件不存在会新建文件,否则清空内容,只允许写入数据(常用) |
w+ | 以读/写方式打开文件,文件不存在将新建文件,否则清空内容,允许读/写数据 |
wb | 以只写方式打开二进制文件,文件不存在将会新建文件,否则清空内容,只允许写入数据 |
wb+ | 以读/写方式打开二进制文件,文件不存在将新建文件,否则清空内容,允许读/写数据 |
a | 以只写方式打开文件,文件不存在将新建文件,写入数据将被附加在文件末尾(保留 EOF 符) |
a+ | 以读/写方式打开文件,文件不存在将新建文件,写入数据将被附加在文件末尾(不保留 EOF 符) |
at+ | 以读/写方式打开文本文件,写入数据将被附加在文件末尾 |
ab+ | 以读/写方式打开二进制文件,写入数据将被附加在文件末尾 |
竞赛中最常用的是r与w两个模式:r读入数据文件,w输出结果文件。需要警惕a(追加写)模式在正式比赛中的隐患——详见下文“竞赛注意事项”。
使用方法
读入文件内容:
freopen("data.in", "r", stdin); // data.in 就是读取的文件名,要和可执行文件放在同一目录下输出到文件:
freopen("data.out", "w", stdout); // data.out 就是输出文件的文件名,和可执行文件在同一目录下关闭标准输入/输出流:
fclose(stdin); fclose(stdout);注:
printf/scanf/cin/cout等函数默认使用stdin/stdout,将stdin/stdout重定向后,这些函数将输入/输出到被定向的文件。
完整模板
#include <cstdio> #include <iostream> int main(void) { freopen("data.in", "r", stdin); freopen("data.out", "w", stdout); /* 中间的代码不需要改变,直接使用 cin 和 cout 即可 */ fclose(stdin); fclose(stdout); return 0; }这份模板就是竞赛“读data.in、写data.out”的标准写法:重定向发生在主函数开头,正文代码保持cin/cout或scanf/printf原样,程序结束时显式关闭两个标准流。
从源码看freopen与快速读入的结合
freopen的重定向能力可以与各类快速读入方案无缝配合。仓库中 快速读入参考实现 是一个基于fread/fwrite的完整IO结构体,其注释明确指出:
若要进行文件读写,需在所有读写进行之前加入
freopen()。
这是因为fread(buf, 1, MAXSIZE, stdin)读取的是标准输入流stdin,一旦用freopen("data.in", "r", stdin)将stdin重定向到文件,快速读入逻辑无需任何改动即可从文件读取数据。从源码结构看,这也是竞赛选手把“文件读写”和“性能优化”解耦的标准做法。
fopen函数(选读)
函数原型
fopen函数大致与freopen相同,区别在于它打开指定文件并返回打开文件的指针,供后续读写函数使用:
FILE* fopen(const char* path, const char* mode)各项参数含义同freopen:path为文件名,mode为打开模式(r/w/a等,见上文模式表)。
可用读写函数(基本)
配合fopen返回的文件指针,可以使用以下基本读写函数:
fread/fwrite:按块读写(二进制与文本皆可)fgetc/fputc:单字符读写fscanf/fprintf:格式化读写fgets/fputs:字符串按行读写
使用方式
FILE *in, *out; // 定义文件指针 in = fopen("data.in", "r"); out = fopen("data.out", "w"); /* do what you want to do */ fclose(in); fclose(out);fopen方案更灵活,但使用前需要判断返回值是否为NULL(文件打开失败时返回空指针),并在读写后逐个关闭指针。
C++ 的ifstream/ofstream文件输入输出流
C++ 标准库提供了面向对象的文件流方案:ifstream(输入文件流)与ofstream(输出文件流),二者均定义在头文件<fstream>中,位于std命名空间。
使用方法
读入文件内容:
ifstream fin("data.in"); // data.in 就是读取文件的相对位置或绝对位置输出到文件:
ofstream fout("data.out"); // data.out 就是输出文件的相对位置或绝对位置关闭文件流:
fin.close(); fout.close();与freopen不同,文件流方案需要把代码中的cin替换为fin、cout替换为fout,即通过改名的方式使用文件。
完整模板
#include <fstream> using namespace std; // 两个类型都在 std 命名空间里 ifstream fin("data.in"); ofstream fout("data.out"); int main(void) { /* 中间的代码改变 cin 为 fin ,cout 为 fout 即可 */ fin.close(); fout.close(); return 0; }如果希望只改动重定向部分而保持正文为cin/cout,可以在main中把标准流与文件流绑定(如std::cin.rdbuf(fin.rdbuf())),不过这已超出本文范围,竞赛中更常见的是直接改名使用。
三种方案的对比与选型
| 方案 | 头文件 | 正文代码是否改变 | 适用场景 |
|---|---|---|---|
freopen | <cstdio> | 不需要,重定向后cin/cout、scanf/printf原样使用 | 竞赛标准写法,最推荐 |
fopen | <cstdio> | 需要改用fscanf/fprintf等带指针的读写函数 | 需要精细控制文件指针、二进制读写 |
ifstream/ofstream | <fstream> | 需要将cin/cout改为fin/fout | 习惯 C++ 面向对象风格的选手 |
竞赛场景中的文件读写注意事项
文件名与路径
freopen()中的文件名必须带扩展名,例如data.in不能写成data——仓库 常见错误 明确指出“freopen()中的文件名未加.in/.out”是本地运行时的常见错误;- 默认情况下,数据文件要与可执行文件放在同一目录下,或使用相对/绝对路径。
慎用追加写模式a
仓库 常见错误 专门警告:freopen使用a进行追加写时,由于 CCF 的检测环境不会清空输出文件,使用a会导致上一位选手的输出也被评测机读入而引发 WA。因此正式比赛一律使用w模式覆盖写,而不要使用a。
文件指针的管理
- 打开的文件用完后务必关闭:
fclose(stdin)/fclose(stdout)或fin.close()/fout.close(); - 对拍等场景中反复
fopen前要记得先fclose,否则会积累大量文件野指针,见 常见错误 中的相关条目。
与标准输入输出的切换
freopen方案下,提交评测(评测机从标准输入读数据)时只需删除或注释掉两个freopen调用即可无缝切换;ifstream/ofstream方案则需要把fin/fout改回cin/cout。这也是竞赛中freopen更受欢迎的原因——切换成本最低。
参考资料
- 信息学奥赛一本通
相关延伸阅读:OI-wiki 文件操作文档、基于流的 I/O 与 C 风格 I/O 优化、竞赛常见错误。
【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考