// ************************************************************ //
// 本文源自飞燕之家在线测评论坛http://yzfy.org/,转载清注明出处 //
// ************************************************************ //
代码调试技巧小集合(C语言描述,但C/C++/Pascal通用)
1.输入重定向
有不少人对自己提交到网站里得到的错误的结果而感到莫名其妙。
但有可能由于题目的输入数据巨多,要是手工输入将会非常累。
例如输入的数据可能多达成千上万。其实以下将要介绍的代码技巧,
对于做ACM题目较多的人来说,他(她)们也肯定会知道的。
本文算是在做普及工作吧。
首先,从手工转为自动方式,最显然的就是从文件读入的数据代替手工。
如果在源代码里希望把输入从屏幕输入定向到一个文件,
可以使用freopen函数(C/C++均支持此方法),以下为示例:
#include <stdio.h>
int main(void)
{
freopen("in.txt","r",stdin); //输入从in.txt
int a,b;
while(scanf("%d%d", &a, &b)!=EOF)
{
printf("%d\n", a+b);
}
return 0;
}
这样你可以把题目的输入内容先保存的in.txt文件里,
在程序运行后看看屏幕输出,看是否和题目的样例输出一样。
从这里也可以看出,输入和输出是完全独立的,
您不必把所有输入或输出保存,直到程序全部计算完毕再输出,
这样会很浪费内存,我们也不希望你这样做。
你应当在你能够确定输出内容的时候就马上输出,能少占用内存就少用。
如果你希望本地调试成功的代码不用修改就可以直接提交到OJ系统上,
那你应该加上如下预处理(OJ系统通用标准,不支持这个特性的OJ就不合格):
#ifdef ONLINE_JUDGE //判断是不是OJ系统上编译
#define FINPUT(file) 0 // 如果是,则不重定向到文件
#else
#define FINPUT(file) freopen(file,"r",stdin)
#endif
#include <stdio.h>
int main(void)
{
FINPUT("in.txt"); //如果不是则输入从本地in.txt
int a,b;
while(scanf("%d%d", &a, &b)!=EOF)
{
printf("%d\n", a+b);
}
return 0;
}
如果你懂得命令行下运行一个程序,那么你可以不用修改源代码,
假设你的代码所生成的程序名是temp.exe,在命令行下运行:
temp.exe<in.txt
一样实现了相同的功能。
如果程序输出内容过多,你需要保存下来分析的话,那么还可以使用输出重定向。
如上方法,你也可以定义这样一个宏:
#define FOUTPUT(file) freopen(file,"w",stdout)
如果输入输出均需要重定向,上面的代码可以改写为:
#ifdef ONLINE_JUDGE //判断是不是OJ系统上编译
#define FINPUT(file) 0 // 如果是,则不重定向到文件
#define FOUTPUT(file) 0
#else
#define FINPUT(file) freopen(file,"r",stdin)
#define FOUTPUT(file) freopen(file,"w",stdout)
#endif
#include <stdio.h>
int main(void)
{
FINPUT("in.txt"); //如果不是则输入从本地in.txt
FOUTPUT("out.txt");
int a,b;
while(scanf("%d%d", &a, &b)!=EOF)
{
printf("%d\n", a+b);
}
return 0;
}
在命令行下则可以:
temp.exe <in.txt >out.txt
2.断言和运行时错误捕获
断言对于初学者来说,可能是一个很新鲜的名词。
但这个东西对于软件调试来说有一个非常有用的东西,
不过使用的时候也得小心,不正确的使用甚至会适得其反。
首先,断言就是在逻辑上,你认为一定会得到的结果,
例如你假定内存分配一定成功,那么返回值一定不为NULL,
在你包含了头文件assert.h或者cassert后,就可以使用
assert(exp);
exp是你需要断言的表达式,bool类型,当exp为true的时候,
断言成功,程序继续执行,否则程序强制停止,并且发出警告。
这样你就可以明确得知程序发生了逻辑问题,必须进行修改。
但问题就在于,如果你直接使用assert(exp)这个函数,
那么这个断言不管在什么情况下都会编译进执行文件,
如果提交到OJ上,并且断言在内循环里,就显得这个断言会占用相当的时间。
如果我们不希望发布正式版本的软件或者提交代码时去掉这个断言以提升速度,
我们又可以用宏封装一下:
#ifdef ONLINE_JUDGE //判断是不是OJ系统,非OJ系统调试可另起名字
#define ASSERT_LEVEL 1
#else
#define ASSERT_LEVEL 0
#endif
#if (ASSERT_LEVEL>=1)
#define ASSERT(exp) _assert(#exp, __FILE__, __LINE__)
#else
#define ASSERT(exp) 0
#endif
double foo(int n) //计算阶乘
{
ASSERT(n>=0);
return n*foo(n-1);
}
#include <stdio.h>
int main(void)
{
int n=10;
printf("%d\n", foo(n));
return 0;
}
以上为一个错误递归算阶乘的程序示例,运行一下你就知道断言的威力了。
那个foo函数应当改为:
double foo(int n) //计算阶乘
{
ASSERT(n>=0);
if(n==0)return 1;
return n*foo(n-1);
}
使用断言还有很多技巧和需要注意的东西,这里暂不多说,先自己体会体会吧。
关于运行时错误捕获,很多人马上就想到了C++的try。但事实上,
这种方法不一定能把异常都捕获,但在OJ系统里面,
你任何一个没有进行处理的异常的抛出都会导致RE(注意main函数返回非0也RE),
尽管有些异常不会对运行结果有影响,操作系统也会忽略掉,
于是一般运行的时候用户也无法发现已经出现异常。
同样,VC也是MS的产品,连操作系统都忽略的异常,用它去调试同样也忽略,
你不能依赖VC调试器得到所有异常的信息,
所以你不要以为VC上调试没有出现任何异常信息就以为那真的是正确的代码,
有可能是系统忽略掉,也有可能是你自己的测试用的数据没有触发它。
对于这个问题目前本人也没有较好的解决办法,所以还是建议你,
养成良好的编程习惯,适当的地方就加上断言,以增加异常被捕获的机率,
这样就更容易发现出错的地方。