您的位置:首页 > 移动开发 > 微信开发

分析网友的一段求救小程序,20行代码有多少错误?多少可改进之处?

2007-11-18 20:23 337 查看
偶然看到一位网友的求救帖子《求教! 在C语言中编译的通 但却实现不了!!!不知道为什么?那位大哥帮帮忙!! 》,看了一下,觉得这是一段比较典型。如果只是简单回答一下,估计无论是他还是别的阅读者收获都不大。

所以我留言希望他能允许俺把他下面的代码作为例子在帖子中使用(虽然他没有回答我,但是他把我加为好友,我想已他明白我的用心),在此非常感谢这位网友的奉献!人非生而知之,每个程序员都有过写这种代码的过程,但区别在于有人永远就这么写下去了,有人不断提高对自己的要求。而把自己的代码贴出来让人批判和分析是非常好的有效学习手段,以后同类的问题应该就不会再犯很多次了。

为了叙述方便,我把原代码都加了行号。

#include
#include
#include
#include
#include
1: void copyfile(char *infile, char *outfile)
2: {
3: FILE *in,*out;

4: in = fopen(infile,"r");
5: out = fopen(outfile,"w");

6: while (!feof(in))
7: {
8: fputc(fgetc(in),out);
9: }

10: fclose(in);
11: fclose(out);
12: }
13: void main()
14: { int done;
15: struct ffblk ffblk;
16: done = findfirst("d://*.txt",&ffblk,0);
17: while (!done)
18: {
19: if (strcmp("d://abcd.txt", ffblk.ff_name) != 0 )

20: {
21: copyfile("d://abcd.txt",ffblk.ff_name);
22: }
23: done=findnext(&ffblk);
24: }
25: }

从代码来看,应该是在Turbo C 的环境下写的,所以我们就按照TC所能支持的来说。

就跟我在前面的帖子的中说过的,写code之前,我们要确定自己真的知道要干什么。所以,作为提问者应该第一句话描述你期待程序要干什么,第二句话描述出什么错了,然后再贴详细的代码等。否则上来一段程序,人家没法回答你的问题。既然缺少这一关键的描述问题的部分,我们只能从程序中猜测。从代码的逻辑中,他大概是想把D盘根目录下abcd.txt文件内所有内容拷贝到其他文本文件中。但是程序没像他所期望的那样执行。

我们先解决langhuo的问题,让这段程序大体正确执行。先看几个比较明显的错误:

#19:strcmp是区分大小写的字符串比较函数。我想langhuo的原意应该是只有找到的文本文件名不是abcd.txt才继续往下执行,但在Windows操作系统中,文件名是不区分大小写的,实际运行中findfirst及findnext返回的文件名很可能是统一的大写,所以这里应该用不区分大小写的比较函数"stricmp"(或strcmpi)。除此以外,这一行也包括下面的错误。

#21: ffblk.ff_name只包括文件名而不包括路径。所以,copyfile("d://abcd.txt", ffblk.ff_name)到底把文件拷贝到哪里去了?在Debug环境下,通常就是TC的安装目录,其他情况下是当前目录。所以你可能没有在d:/盘下发现期待的输出文件,去TC的安装目录下看看,可能在哪里呢!如果直接执行EXE的话,结合#19的错误,就会产生从abcd.txt拷贝到abcd.txt的情况,实际结果是执行到#5行的时候abcd.txt被清空。

#25: 按照要求,调用findfirst和findnext之后应该调用findclose()函数释放资源。使用C语言的时候一定记住,用完的资源(内存、文件等)一定想着问问自己:我干完活清理现场了吗?可不要把犯罪现场留下啊。

#4: 文件读模式打开可能失败,例如你没有读某个文件的权限,应该检查返回值。

#5: 文件写模式打开可能失败,例如只读文件、权限不够等,应该检查返回值。如果#4、#5行出现错误,后面的代码又是读又是写的,会出现什么现象?可能什么事也不会发生,可能......后果自负!

#10,11: 因为文件未必正常打开,所以直接调用flcose可能出错。fclose的函数说明里明确指出如果传给fclose的参数为NULL,它会调用出错处理机制,在有些系统里没事,但在另外一些系统里可能就有事。

更正上面的错误以后,应该大体可以看到我们期望的结果了。但是我们会惊奇地发现,新拷贝的文本文件末尾多了一个奇怪的字符!而且每拷贝一次,文件大小会增加一个字节。这是怎么回事?这是因为一个隐含的错误藏在#8行里!

#8: 这行的写法在很多垃圾教科书里都能看到,但很遗憾在很多平台上运行时这句话都是错误的!这就是抄书的后果。原因很简单,看看feof的说明:"The feof function returns a nonzero value after the first read operation that attempts to read past the end of the file. ",也就是在第一次尝试读取文件结束之后它才会返回一个非零值表示"读过头了"。而我们知道文本文件末尾有一个EOF标志字节(0xFF),在尝试读这个字节之前,feof不会告诉你到头了,只有等你读过这个EOF之后,它才告诉你刚才读过头了。所以最后等循环到#6行feof返回"过头"的时候,你已经把多写一个字符了!解决方法有很多,一种是先读再判断feof,在while循环前就先读一个字符。而且在这段程序里,如果只是处理文本文件,也可以不用feof,只要判断读出来的字符是否是EOF就可以。(但这样做的结果是,遇到非文本文件就不对了)

如果以上错误都能改正的话,如果俺是学校里的C语言老师,可能会给60分(所以我不敢去现在的大学里当老师,怕被砸死,呵呵),因为我看到作者知道把相对独立的拷贝功能放在一个函数里而不是一个长长的main()。为什么只有60分呢?还有什么问题吗,有!

1. 第1行的函数定义里,两个参数都是只读的,应该加上const修饰符,写成"const char *infile, const char *outfile"。否则不仅存在被误写的可能,而且在按照下面一条修改的时候编译器会报"警告"(严格的程序员把警告当错误一样对待)。

2. 第16、19、21行的代码里不应该出现hardcode的字符串,应该在文件头统一定义,如 const char* INPUT_FILE = "d://abcd.txt"; (也可用#define,但不推荐)。这样在第19、第21行就可以用这个INPUT_FILE来代替具体的字符串,方便修改,减少出错机会。

3. 第15行"struct ffblk ffblk"有两个问题,一是变量名和结构名相同,而是定义结构变量以后要养成初始化的习惯(其它变量也一样)。

4. 你怎么确定一定存在"D"盘?文件名和路径最好通过main的入口参数提供,并且要检测要处理的目录是否存在。

5. 逻辑上应先检查源文件abcd.txt是否存在,如果不存在,后面循环很多次都是白干。

6. 如果只处理文本文件,应该在fopen打开文件时加上文本文件标志"t"。如果处理所有文件应加上"b"。

7. 上面的copyfile函数即便改正了我说过的错误,在拷贝非文本文件会出错,因为数据文件里有很多字节是0xFF,会提前结束。

8. 读一个字节写一个字节效率极低,建议改成fread和fwrite用缓冲方式,一次读写若干K数据。而且fread会返回实际读到的字符个数,不会出现多写一个EOF的问题,而且可以处理二进制文件。

9. Turbo C内部库函数名和结构名是小写,我们自己的变量名和函数名最好与之不同,以便一眼就能看出哪里是你自己的,哪些是他的。

10. 书写时函数参数之间、赋值等地方应该加空格。例如"done=findnext(&ffblk);"要写成"done = findnext (&ffblk);"

写到这个程度大概可以有75~80分了。还有什么呢?

1. copyfile作为函数不应假设调用者会怎么做,它第一行就要检查参数是否合法。

2. copyfile函数应该有个简单说明,它是干什么的,它能处理什么,包括对参数的假设:只处理文本文件,把第一个参数的文件内容拷贝到第二个文件中。

3. copyfile函数应该有返回值告诉调用者是否成功。

4. 作为整个程序,应该有错误报告部分,要告诉使用者main入口参数是什么格式,遇到文件无法拷贝(如只读、权限不够)等情形要告诉使用者在处理哪个文件时出什么错了?有些代码要加调试标志,只在debug版本执行。

5. 在原程序#6~#8中间可能会出现严重错误导致程序退出,而相应关闭文件的代码没有机会执行。可能会出现损坏的文件以及文件处于打开状态无法删除。

6. 因为#21行可能出错,导致系统资源没有机会释放。

作为在校学习C语言的大一学生,我觉得如果能写到这个程度就差不多了,可以给90分。但是,如果是大四的学生,要找工作了,那就不行了,因为以上要求受限于Turbo C的能力,真正工作中到这个地步还不及格呢!因为企业不会问你老师教过什么,也不care书上怎么说的,他关心的是你的代码能不能在产品开发中使用。如果不能使用,那就是不及格。所以,大一以后就不要用Turbo C(虽然我也怀念这个优秀的TC2.0)了,要按现实世界的要求来做。

现实工作中,就上面这20来行代码,除了不能犯上面已经说过的所有问题以外,还会遇到哪些问题?

- 调用不区分大小写的函数strcmpi 来比较两个分别为大写和小写的串("I AM YONG", "i am yong"),通常返回值相等,但至少有一种情况不相等,你知道吗?我知道的是在土耳其语言平台这两个串不相等!因为在土耳其语里小写的"i"对应的大写字符不是"I"!你最好别用这几个C字符串库函数,几个都有毛病。

- 应该全程使用UNICODE,从main函数开始。

- 考虑一下如果要处理的文件在一个可热插拔的硬盘上,你的代码正执行在6~9行的时候,我把硬盘给拔了:-P

- 在VC里findfirst、findnext与TC里的参数及返回值都不同

- 在VC里返回的是handle,而done定义的是int类型,在64位平台上这是错的

- 需要使用智能指针来对付应外退出时的清场工作

- Debuger代码应该更加结构化

- 作为独立函数,需要有相应的测试代码

- 在Windows平台上,使用C 文件操作函数读写效率不高。

- 如果是很大的文件呢?比如是一个文件1G,这么干效率好吗?10G呢?

我们经常在讨论这样那样高深的算法,折腾质数啊、兔子啊、海盗啊,还有这样那样的新技术例如什么.Net,SiliveLight,Web2.0啊等等,这样那样尖端领域如人工智能、自然语言处理、图形图像等等......但是,我最期望的是当我们本科毕业的时候,至少可以写一段合格的代码,然后再谈其他。所以希望大学里教程序设计的老师对学生能进行严格的逻辑思维训练,从一开始就书写正确、易懂的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐