文章

【信奥业余科普】C++ 的奇妙之旅 | 28:规范比赛代码的钥匙——文件操作与输入输出重定向(freopen)

在前面的文章中,我们介绍了 C++ Standard Template Library (STL) 中的通用算法库 <algorithm>。掌握了这些常用算法后,我们已经能够写出非常高效的数据处理代码。

然而,在实际参加信奥竞赛(如 CSP-J/S、NOIP 等)时,仅仅写出逻辑正确的代码是不够的。信奥比赛有一条极其严格的硬性规定:程序必须通过文件进行数据的输入与输出

如果你没有按照要求进行文件操作,或者把文件名拼写错了哪怕一个字母,测评机都将无法读取你的输出,最终导致该题直接判定为 0 分(俗称“爆零”)

今天,我们就来学习如何使用 C++ 中最简单、最常用的文件输入输出重定向函数 —— std::freopen,为你的比赛代码保驾护航。

往期回顾:


一、 为什么比赛需要文件输入输出?

在我们平时的编程练习中,通常是在控制台(终端)中输入数据,然后直接在屏幕上查看输出结果。这被称为 标准输入输出(Standard I/O)

  • 标准输入(stdin:默认是键盘。
  • 标准输出(stdout:默认是屏幕。

但是在信奥比赛中,成千上万名选手的代码需要提交到测评系统进行自动阅卷。如果每个人都需要手动输入测试数据,测评系统将无法工作。因此,比赛采用文件读写的方式:

  1. 测评系统将测试数据放在一个特定的输入文件中(例如 sum.in);
  2. 选手的程序运行后,自动读取该文件;
  3. 程序计算出结果后,必须将结果写入到一个特定的输出文件中(例如 sum.out);
  4. 测评系统比对选手的输出文件与标准答案文件,从而给出分数。

要在 C++ 中实现这一点,最轻松、改动最小的方法就是使用 输入输出重定向


二、 核心利器:std::freopen

std::freopen 定义在头文件 <cstdio> 中。它的作用是重新定向标准输入输出流。

2.1 函数原型与语法

1
std::freopen(const char* filename, const char* mode, std::FILE* stream);

对于信奥选手,我们只需要记住以下两行固定搭配:

1
2
3
4
5
// 1. 将标准输入重定向到指定文件(只读模式 "r")
std::freopen("problem.in", "r", stdin);

// 2. 将标准输出重定向到指定文件(只写模式 "w")
std::freopen("problem.out", "w", stdout);

参数解析:

  • filename:目标文件名(如 "problem.in""problem.out")。必须与比赛题目要求的英文名称完全一致。
  • mode:文件打开模式。"r" 代表只读(Read),"w" 代表只写(Write,若文件已存在则会覆盖)。
  • stream:要重定向的流。stdin 代表标准输入,stdout 代表标准输出。

[!NOTE] 使用 freopen 的最大好处在于:你的核心代码不需要做任何修改。你依然可以使用熟悉的 std::cin / std::cout 或者 std::scanf / std::printf 进行数据读写,系统会自动帮你在后台完成文件对接。


三、 实战演练:以 A+B Problem 为例

我们以最基础的“两数求和”为例,要求从 aplusb.in 中读取两个整数,并将它们的和输出到 aplusb.out 中。

3.1 完整代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <cstdio> // 必须引入此头文件以使用 freopen

int main() {
    // 1. 在 main 函数的最开始加入重定向
    std::freopen("aplusb.in", "r", stdin);
    std::freopen("aplusb.out", "w", stdout);

    // 2. 正常编写你的业务逻辑代码
    int a, b;
    if (std::cin >> a >> b) {
        std::cout << a + b << std::endl;
    }

    // 3. (可选但推荐) 规范地关闭文件指针
    std::fclose(stdin);
    std::fclose(stdout);
    return 0;
}

3.2 本地运行与测试步骤

如果你直接在集成开发环境(IDE,如 Dev-C++、Code::Blocks 或 VS Code)中点击“运行”:

  1. 控制台窗口不会等待你输入,因为它已经把输入源改为了 aplusb.in
  2. 因为在同一目录下没有创建 aplusb.in,程序读取不到数据,控制台可能直接闪退或结束。
  3. 同时,你会发现在生成的 .exe 文件的同级目录下,多出了一个名叫 aplusb.out 的空文件。

💡 正确的本地测试方法:

  1. 在项目(可执行文件)所在的文件夹下,新建一个文本文件,命名为 aplusb.in
  2. 用记事本打开 aplusb.in,输入两个数字(例如 12 34),保存并关闭;
  3. 运行你的 C++ 程序;
  4. 程序运行结束后,打开同目录下新生成的 aplusb.out 文件,你会发现里面已经写好了答案 46

四、 本地调试与提交的切换技巧

在写代码时,为了方便调试,我们通常希望在本地运行时能通过屏幕 and 键盘交互,而提交给测评机时又自动使用文件重定向。

这里介绍两种常用的处理方法:

4.1 方法一:手动注释(最安全、最适合初学者)

在写代码和本地调试时,将 freopen 的行注释掉。当确认代码逻辑无误、准备提交前,务必解开注释

1
2
3
4
5
6
7
8
9
10
int main() {
    // 本地调试时注释掉下面两行,手动在控制台输入数据
    // std::freopen("sum.in", "r", stdin);
    // std::freopen("sum.out", "w", stdout);

    int a, b;
    std::cin >> a >> b;
    std::cout << a + b << std::endl;
    return 0;
}

[!WARNING] 这种方法虽然稳妥,但最怕的就是交卷前忘记解开注释。忘记写或忘记解开 freopen 的注释,通常会导致整道题直接得 0 分。

4.2 方法二:条件编译(适合熟练的选手)

我们可以利用预处理器指令 #ifndef(If Not Defined)来自动控制。在大多数 Online Judge(如洛谷、POJ、CF等)上,测评系统会默认定义一个名为 ONLINE_JUDGE 的宏,而选手的本地电脑上没有定义这个宏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstdio>

int main() {
    // 如果不是在线评测,就执行文件重定向(即本地调试时读取文件)
    #ifndef ONLINE_JUDGE
    std::freopen("sum.in", "r", stdin);
    std::freopen("sum.out", "w", stdout);
    #endif

    int a, b;
    std::cin >> a >> b;
    std::cout << a + b << std::endl;
    return 0;
}
  • 本地运行:因为没有定义 ONLINE_JUDGE,重定向生效,程序读取 sum.in 并写入 sum.out
  • 提交到 OJ 评测:因为 OJ 自动定义了 ONLINE_JUDGE,这部分代码被自动忽略,程序依然使用标准输入输出。

[!IMPORTANT] 特别提醒:对于 CSP-J/S、NOIP 等线下复赛,评测是在官方提供的离线测试环境(测评机)中进行的,官方评测机不一定会定义 ONLINE_JUDGE

因此,在 CSP-J/S 复赛中,最稳妥的做法是:绝对不要使用条件编译来提交。必须在提交最终代码前,老老实实解开 freopen 的注释,确保那两行重定向代码直接暴露在外面!


五、 信奥赛场上的四大“致命避坑指南”

根据历届信奥比赛的数据统计,每年都有大量选手因为文件读写错误而惨遭“爆零”。请大家务必牢记以下几点:

5.1 严防文件名拼写错误

文件名多一个字母、少一个字母、大小写写错,均会导致系统找不到文件。

  • 例如题目要求是 apple.in,你写成了 aple.in,或者写成了大写的 Apple.in
  • 避坑法宝:在写完代码后,从题目 PDF 电子文档中直接复制文件名,然后粘贴到你的 freopen 中。

5.2 严防 Windows 系统隐藏后缀名

在 Windows 电脑上,默认会隐藏文件后缀名。如果你新建一个文本文件并命名为 sum.in,由于隐藏了后缀,它的真实名字其实是 sum.in.txt

  • 这会导致你的程序本地读取不到 sum.in 从而报错。
  • 避坑法宝:在 Windows 文件资源管理器中,点击顶部的“查看”菜单,勾选 “文件扩展名”,确保能看到文件的完整后缀。

5.3 严禁写绝对路径

freopen 中,只写文件名即可,例如 "apple.in",这表示相对路径(在当前程序运行的文件夹中寻找)。

  • 千万不要写绝对路径(例如 "D:\\csp\\apple.in"),因为评测系统上的目录结构与你的个人电脑完全不同,写绝对路径会导致评测机找不到文件。

5.4 输入输出流加速的配合使用

在前面的文章中我们提到,可以使用以下代码加速 std::cinstd::cout 的效率:

1
2
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);

这两行加速语句与 freopen 并不冲突,两者可以同时使用。通常把加速语句写在 freopen 的下方即可。


结语与下期预告

本文介绍了 C++ 竞赛中至关重要的文件重定向操作 freopen。文件操作是信奥竞赛的入场券,不容许出现一丝一毫的马虎。希望大家在平时的练习中就养成规范书写文件读写的习惯。

下一篇文章中,我们将详细讲解 【衡量代码效率的标尺——时间复杂度与空间复杂度(以实例说话)】,带大家学会如何估算代码的运行时间与内存,避免在竞赛中超时失分。

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

"luogu-"系列题目可在洛谷题库进行在线评测。

"bcqm-"系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权