注:本文根据《算法入门经典第(2)版》撰写

前言

由于程序需要测试大量用例,手动输入较为麻烦。因此可以使用文件操作。一个好的方法是用文件——把输入数据保存在文件中,输出数据也保存在文件中。这样,只要事先把输入数据保存在文件中,就不必每次重新输入了;数据输出在文件中也避免了“输出太多,一卷屏前面的就看不见了”这样的尴尬,运行结束后,慢慢浏览输出文件即可。如果有标准答案文件,还可以进行文件比较,而无须编程人员逐个检查输出是否正确。事实上,几乎所有算法竞赛的输入数据和标准答案都是保存在文件中的。

示例中采用创建date.in文件存储输入,date.out存储输出。

一、文件重定向

使用文件最简单的方法是使用输入输出重定向,只需在 main 函数的入口处加入以下两条语句:

  • freopen(“input.txt”, “r”, stdin);
  • freopen(“output.txt”, “w”, stdout);

上述语句将使得 scanf 从文件 input.txt 读入,printf 写入文件 output.txt。事实上,不只是 scanf 和 printf,所有读键盘输入、写屏幕输出的函数都将改用文件。尽管这样做很方便,并不是所有算法竞赛都允许用程序读写文件。甚至有的竞赛允许访问文件,但不允许用freopen 这样的重定向方式读写文件。参赛之前请仔细阅读文件读写的相关规定。

利用文件是一种很好的自我测试方法,但如果比赛要求采用标准输入输出,就必须在自我测试完毕之后删除重定向语句。
但是还有一种操作就是条件编译。

1
2
3
4
5
6
7
8
9
#define LOCAL 
······
int main(){
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
······
}

该操作通过定义宏来规避影响,因为许多oj评测,编译时会定义ONLINE_JUDGE宏。如果有检测到这个宏,就不会运行重定向操作,这样就可以在本地使用文件输入输出。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define LOCAL 
#include<stdio.h>
#define INF 1000000000
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
int x, n = 0, min = INF, max = -INF, s = 0;
while(scanf("%d", &x) == 1)
{
s += x;
if(x < min) min = x;
if(x > max) max = x;
/*
printf("x = %d, min = %d, max = %d\n", x, min, max);
*/
n++;
}
printf("%d %d %.3f\n", min, max, (double)s/n);
return 0;
}

这是一份典型的比赛代码,包含了几个特殊之处:

  • 重定向的部分被写在了#ifdef 和#endif 中。其含义是:只有定义了符号 LOCAL,才编译两条 freopen 语句。
  • 输出中间结果的 printf 语句写在了注释中——它在最后版本的程序中不应该出现,但是又舍不得删除它(万一发现了新的 bug,需要再次用它输出中间信息)。将其注释的好处是:一旦需要时,把注释符去掉即可。

上面的代码在程序首部就定义了符号 LOCAL,因此在本机测试时使用重定向方式读写文件。如果比赛要求读写标准输入输出,只需在提交之前删除#define LOCAL 即可。一个更好的方法是在编译选项而不是程序里定义这个 LOCAL 符号(不知道如何在编译选项里定义符号的读者请参考附录 A),这样,提交之前不需要修改程序,进一步降低了出错的可能。
提示 :在算法竞赛中,有经验的选手往往会使用条件编译指令并且将重要的测试语句注释掉而非删除。

fopen版

如果比赛要求用文件输入输出,但禁止用重定向的方式,又当如何呢?程序如下:
数据统计(fopen 版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 #include <stdio.h>
int main()
{ FILE *fin, *fout;
fin = fopen("data.in", "rb");
fout = fopen("data.out", "wb");
int n, u, d, i, time = 0;
while (fscanf(fin,"%d %d %d", &n, &u, &d) !=EOF && (n!=0))
{
if (n <= u)
time = 1;
else
{ for (i = 1;; i++)
{
if (n <= ((u - d) * i + u))
break;
}
time = i * 2 + 1;
}
fprintf(fout,"%d\n", time);
}
fclose(fin);
fclose(fout);
return 0;
}

虽然新内容不少,但也很直观:先声明变量 fin 和 fout(暂且不用考虑 FILE *),把 scanf改成 fscanf,第一个参数为 fin;把 printf 改成 fprintf,第一个参数为 fout,最后执行 fclose,
关闭两个文件。
提示 :在算法竞赛中,如果不允许使用重定向方式读写数据,应使用 fopen 和 fscanf/fprintf 进行输入输出。
重定向和 fopen 两种方法各有优劣。重定向的方法写起来简单、自然,但是不能同时读写文件和标准输入输出;fopen 的写法稍显繁琐,但是灵活性比较大(例如,可以反复打开并读写文件)。顺便说一句,如果想把 fopen 版的程序改成读写标准输入输出,只需赋值“fin =stdin; fout = stdout;”即可,不要调用 fopen 和 fclose。