算法程序的三部曲是:输入、计算、输出。
例题:求圆柱体的表面积。
#include <stdio.h>
#include <math.h>
int main()
{
const double pi = acos(-1.0);
double r, h, s1, s2, s;
scanf("%lf%lf", &r, &h); //输入
s1 = pi * r * r; //开始计算
s2 = 2 * pi * r * h;
s = s1 * 2.0 + s2;
printf("Area = %.3f\n", s); //输出
return 0;
}
在一个算法中,程序的执行是自动完成的,不需要人工干涉,所以不要在用户输入之前打印提示信息(例如:Please input r:),这样及时结果计算正确,算法也不能通过,因为这些信息会被认为是输出数据的一部分,和标准结果不符合。
其次也不要让程序“按任意键退出”,比如调用 system("pause")
或者 getchar()
,因为在程序自动执行时,不会有人来“按任意键”的。因为在早期某些教材中可能会建议在程序最后添加这样一行来观察输出结果,但是不要在算法的 OJ 系统中使用。
一般情况下,程序不能直接读取键盘和控制屏幕:不要在算法中使用头文件 conio.h, 包括 getch()、getche()、gotoxy() 和 clrscr() 函数。
算法的输入输出格式非常严格,每行输出应该以回车符结束,包括最后一行。除非特别声明,每行的行首不应有空格,但行末通常可以有多余空格。输出的每两个数或字符串之间应该以单个空格隔开。
算法中的输入输出框架
如下程序:
输入一些整数,求出它们的最小值,最大值和平均数(保留 3 位小数)。输入保证这些数都是不超过 1000 的整数。
分析:如果是先输入整数 n,然后输入 n 个整数,输入上应该不会有问题。但是这个题目的输入的个数是不确定的。程序如下:
#include <stdio.h>
int main()
{
const int INF = 1000000000;
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;
++n;
}
printf("%d %d %.3f\n", min, max, (double)s/n);
return 0;
}
程序编译运行后,开始输入:2 8 3 5 1 7 3 6,按 Enter 键盘,但是未显示结果?其实程序还处于等待输入中,因为 scanf 的输入格式中,空格、Tab、回车符都是无关紧要的,所以按下 Enter 并不意味着结束。
要如何告诉程序输入结束呢?
- Windows 下,输入完毕后按下回车,再按
Ctrl+Z
,最后再按回车键,即可结束。 - Linux 下,输入完毕后按
Ctrl+D
键即可结束输入。
管道输入
手动输入数据会影响程序耗时
当我们程序运行后,程序的 Time used 在键盘输入的时间也会被计算在内,因为这是程序启动后进行的,为了避免输入数据的时间影响测试结果,可以使用一种称为“管道”的小技巧。
在 Windows 下执行 echo 20 | abc
,操作系统会自动把 20 输入,其中 abc 是程序名,Linux 上也一样。
文件重定向输入
虽然可以手动输入,并且可以正常结束输入,但是每次测试都需要输入许多数。尽管上面说的可以使用管道的方法,但是数据只是保存在命令行中,每次运行都要输入,依旧不够方便。
一个好的方法是使用文件重定向。也就是将输入数据保存在文件中,输入数据也保存在文件中。这样只需要事先将输入数据保存好,以后的运行就不需要每次重新输入了,数据输出在文件中也避免了“输出太多,一卷屏前面的就看不见了”这样的尴尬,运行结束后,慢慢浏览输出文件即可。而且所有标准答案文件,还可以与运行文件比较,无需逐个检查输出文件是否正确。
使用文件最简单的方法是使用输入输出重定向,只需要在 main 函数的入口处加两条语句:
freopen("input.txt", "r", stdin);
freopen("output.txt", "r", stdout);
这两句会将所有从键盘输入、输出到屏幕的函数都将改用文件。尽管这样很方便,但不是所有的竞赛或者算法题都允许用程序读写文件。如果是竞赛,需要参赛选手先弄清楚比赛规定。
利用文件是一种很好的自我测试方法,但如果题目要求采用标准输入输出,就必须在自我测试完毕之后删除重定向语句,不要忘记。
还有一种方法可以在本地测试时使用文件重定向,但一旦提交到比赛,就自动“删除”重定向语句,代码如下:
#define LOCAL
#include <stdio.h>
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "r", stdout);
#endif
const int INF = 1000000000;
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;
++n;
}
printf("%d %d %.3f\n", min, max, (double)s/n);
return 0;
}
上面的代码,重定向的部分被写在了 #ifdef 和 #endif 中。其含义是:只有定义了符号 LOCAL,才编译两条 freopen 语句。
如果题目要求读写标准输入输出,只需在提交之前删除 #define LOCAL 即可。
一个更好的方法是在编译选项而不是程序里定义这个 LOCAL 符号。
在 gcc/g++ 中有一个 -D 选项,用来在使用 gcc/g++ 编译时定义宏的,比如
gcc -DDEBUG
表示定义这个宏,如果代码中使用了这个宏来条件编译,则 DEBUG 宏相当于是一种开启或关闭的开关。另外比如 gcc -Dname1=name2 这种,表示将程序中定义的 name1 全替换成 name2,通常用于测试环境,比如将 private 变为 public 更方便与私有成员函数的白盒测试。
使用编译选项提交之前不需要修改程序,进一步降低了出错的可能。
同时一些重要的测试语句可以注释掉,而并非删除,因为万一发现了新 bug,需要再次用它输出信息。