#include <stdio.h>
int main()
{ void print_star();
void print_message();
print_star();
print_message();
print_star();
return 0;
}
void print_star()
{
printf("******************\n");
}
void print_message()
{printf("How do you do!\n");
}
运行结果:
******************
How do you do!
******************
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p171
甲:这么短的一段代码,会有什么问题吗?
乙:所谓“山不在高”,代码有没有问题也不在于长短。这段代码确实有问题,而且问题还不少。
甲:我怎么没看出来,好像除了在“{”后面直接写“void print_star();”这种风格非常怪异,并没有什么错误啊?
乙:其实你如果仔细观察一下就不难发现这个运行结果其实是伪造的。程序并没有要求在输出“How do you do!”时在前面加任何空白字符。
甲:果然是的,结果和代码根本对不上。
乙:程序本来是要求计算机完成人们所要求的工作,程序的意义也正在于此。现在这个结果显然出自人类的越俎代庖。这和编程的本质是相背离的,说是一种欺骗也不过分。
甲:怪不得有人给会计学校的题词居然是“不做假账”,看来以后说不定会有人给计算机学校题词“不伪造结果”。
乙:确实有必要,这年头假货太多。从“我的成功可以复制”,一直到有些书上的运行结果也可以伪造。
甲:除此之外还有什么问题吗?编译没有什么错误啊?
乙:只有那些崭新得闪闪发光的新手才特别重视编译错误,并且往往把编译错误当作程序中唯一一种错误。稍有经验的新手一般都会觉得这种编译错误也就是语法错误没什么了不起,因为借助编译器很快就能发现,可以马上纠正。他们比较头疼的往往是那种逻辑错误,就是心里想让程序完成的事情,但程序给出的是另外的结果。这种情况往往是由于初学者对语言理解的不够精确,代码写得词不达意或算法有漏洞或瑕疵造成的,这种错误更难纠正——除非敢于替程序伪造运行结果。
甲:这个题目如果把
printf("How do you do!\n");
改成
printf(" How do you do!\n");
应该不再存在逻辑错误了吧?
乙:从程序功能的角度来说
#include <stdio.h> int main( void ) { puts(******************"); puts (" How do you do!"); puts(******************");
return 0; }
这样就足够了,简单又纯粹,根本就没必要自己再定义函数。自己定义函数属于出力不讨好的行为。
甲:倒也是,现在看来确实没有必要。如果不考虑这一点的话,代码还存在哪些问题呢?
乙:函数原型——也就是有时候所说的函数声明,写的有些不伦不类之感。
甲:怎么不伦不类了呢?
乙:void print_star();和void print_message();这两个函数原型的()内应该写void,
void print_star(void); void print_message(void);
里面什么也不写,通常是订立C标准之前老式C语言函数声明的写法。
甲:感觉按照老式写法也有好处啊,至少少写了几个字母。
乙:如果按照老式的写法,这种用不到函数返回值的函数的声明根本就没必要写。
甲:那编译器不是把这两个函数当作返回值为int类型的函数了吗?
乙:是的。但C标准之前根本就没有void这个关键字,因此只要不使用这两个函数的返回值它们的返回值是int类型也无所谓。
甲:哦,就是说void print_star();前面的void是标准后的新风格,而后面()里什么都不写是标准前的旧风格。所以有点不伦不类,是吗?
乙:正是这样。有点戴假辫子穿西装的假洋鬼子气派。
甲:但按照新式风格写似乎不很简洁啊?
乙:这会有相应的补偿。函数原型的意义在于希望编译器帮助检查函数调用时参数类型、个数及函数返回值类型方面是否存在错误。比如,如果一旦函数调用误写成了
print_star(5,6);
这种错误的写法,当函数原型的()里面写了void时,这个错误立刻就能检查出来。
而当函数声明的()里面什么都不写的情况下,这个错误就检查不出来。所以void print_star();这种半旧半新的写法,即没有获得旧风格的好处(可以完全不写函数声明),也没有获得新风格的好处。相当于写了一个毫无意义毫无益处的函数原型。写一个毫无意义的函数原型难道不是很滑稽么?这情形就和在代码中多定义几个毫无用处的变量是一样的。
甲:嗯,多定义几个没有用的变量确实显得有点傻。但如果我执意按照旧风格写——索性不写那种函数说明或者不在()内写任何东西,因为我觉得这样可以少打几个字,标准不也同样容许吗?这样行不行呢?
乙:首先,C标准(C89)加入函数原型这个概念自有其道理,执意守旧从长远和大局的角度来看因小失大、得不偿失。即使是C语言的发明人也强烈建议使用函数原型这种新的编程方式。其次,C标准之所以还容许旧的风格是因为已经有了无数的旧风格的C代码在运行,容许绝对不代表提倡。淘汰旧有的落后的东西有时需要一段时间。C99在这方面的要求就更加严格了。比如在C89中你可以写
int print_star();
或者干脆不写这个函数声明。不写的原因是C89把没声明过的函数都当作返回值为int类型的函数,亦即int在很多情况下可以省略。但C99就不容许省略int,所以在执行C99标准的编译器上不写函数原型的可能性就不存在了。
从这里可以看得出C语言的进化趋势,清清楚楚地写出函数原型才是先进生产力的代表。
甲:那就是说前面的代码应该这样写吗?
#include <stdio.h> int main() { void print_star(void); void print_message(void); print_star(); print_message(); print_star();
return 0; } void print_star() { printf("******************\n"); } void print_message() { printf(" How do you do!\n"); }
乙:main()、print_message和print_star()的函数定义的()内也应该写void。所谓清清楚楚意味着应该尽量不要使用潜规则。
甲:哦。还有别的吗?
乙:还有这个代码中函数原型的位置也值得说说。在这段代码中,两个函数原型都写在了main()函数之内,这是违背常理的。
甲:何以见得呢?
乙:因为定义函数的目的就是为了让其他函数使用。样本代码中把函数声明写在了main()之内,根据作用域准则,这就意味着这些函数仅仅在main()中得到了说明,在其他函数中由于没有得到说明而不能调用直接调用。这种作茧自缚式的自我限制显然与定义函数的本意是背道而驰的,是在自己捆自己的手脚。这是典型的程序结构劣化。
这种写法非常猥琐,一方面把main()函数内搞得凌乱不堪,另一方面还限制妨碍了其他函数直接调用这两个函数——除非你在其他函数中再写一次函数声明。
甲:那应该写在何处呢?
乙:常规的写法是写在所有函数定义之前。
#include <stdio.h> void print_star(void); void print_message(void); int main(void) { print_star(); print_message(); print_star();
retrun 0; } void print_star(void) { printf("******************\n"); } void print_message(void) { printf(" How do you do!\n"); }
这样的话在各个函数体中都可以随时调用这两函数。在适当的时候还便于把这些函数原型组织到*.h文件中。
甲:*.h中的h据说是head的意思,就是因为这些东西通常写在源文件的前面的缘故吗?
乙:可能吧。