到处忙活与一劳永逸
如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明(declaration)。
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p180
这段文字中包含若干概念性错误。
首先,“函数的位置”是一个含混不清的概念,函数定义(Function definition)、函数调用(Function call)和函数类型声明(Function type declaration)等等都和函数有关,“函数的位置”说的究竟是哪一个应该明确,不可以一概而论。不过从上下文看,这里说的似乎应该是函数定义(Function definition)。
其次,在函数调用前对函数进行声明与函数定义写在哪里无关。因为函数定义本身就是一种函数声明(注:C++里函数声明和函数定义不是一回事)。如果把函数定义写在函数调用之前,就等于已经作了声明。如果函数调用之前没有函数定义,则应该在函数调用之前进行声明,这个声明可以不是函数定义,而是不带函数体的那种声明——函数类型声明(Function type declaration)。
第三,在函数调用前对函数进行声明和函数定义是否在同一个文件无关。
第四,在函数调用前进行函数声明,和是否“使用用户自己定义的函数”无关。无论是什么样的函数,在调用前都应该进行声明。这在C90是一个建议,在C99则是一种强制性的要求。因为C90当函数返回值为int类型时在调用前可以不用声明(尽管不提倡),而C99则要求任何函数在调用前都必须声明。调用库函数可以通过#include <???.h>实现函数声明,因为库函数的函数类型声明就写在编译系统提供的那些“???.h”文件之中。
第五,样本中那段文字最大的误导在于,“应该在主调函数中对被调用的函数作声明(declaration)”,C语言只要求在函数调用之前进行声明,根本就没有要求把这个声明写在所谓的“主调函数”的函数体内。把声明写在函数体内反而有许多弊病——尽管不存在语法错误。
那么,函数声明究竟应该写在哪里呢?考察并对比一下下面的两种方案:
1. 写在函数内
type1 g(…) { type_f fun(…); …… } type2 h(…) { type_f fun(…); …… } type_f fun(…) { …… }
2. 写在函数外
type_f fun(…); type1 g(…) { …… } type2 h(…) { …… } type_f fun(…) { …… }
第一种方案:函数类型声明写两次;第二种方案:函数类型声明写了一次。哪种省力就不用多说了,这里说下另一个方面。有句老话叫常在河边走,难免会湿鞋:代码写的越多,出错的可能性也就越大。当然,你也可能是用同一个原本四处拷贝的,然而,相同的代码四处拷贝是一种恶劣的代码写法早有定论。
第一种方案:g()函数中的调用和h()函数中的调用参照的是不同的函数声明,存在不同的函数声明有差异的风险;第二种方案:g()函数中的调用和h()函数中的调用参照的是相同的函数声明。一致性好。如果考虑到以后有对fun()函数进行修改的可能,哪一种的可修改性即可维护性更佳是不言而喻的吧。
第一种方案:g()函数和h()函数都比较臃肿,实际上两个fun()函数类型声明与g()函数和h()函数的功能是根本无关的东西,因为函数类型声明是面向编译器的,而不是面向代码或代码读者的;第二种方案:g()函数和h()函数都很简洁,不存在庸余的赘物。
最后,第二种方案可以在函数类型声明比较多的时候,把它们移出源文件组织成一个“head_name.h”文件——毕竟头顶上的函数类型声明过多有碍代码的视觉美感,而在原地只需要写一条预处理命令
#include " head_name.h "
即可。而第一种方案在这方面完全是束手无策无能为力。
好了,萝卜青菜各有所爱。相信各位完全有能力根据自己的判断做出选择。这里在小结一下:
1. 函数调用之前应该对函数声明,C90是半强制的(函数返回值类型为int可不声明),C99是强制性的。但C语言从没有规定应该在“主调函数”的函数体内声明。
2. 函数定义也是一种函数声明,不带函数体的函数声明叫做函数类型声明。
说到这里,也许有人会想到第三种方案:
type_f fun(…) { …… } type1 g(…) { …… } type2 h(…) { …… }
这种方案,用函数定义这种方式进行函数声明,省写了一次函数类型声明,貌似更好。然而这种方案失去的是代码结构的那种灵活和洒脱。因为你需要费尽心思考虑斟酌应该把那个函数定义放在前面,哪个放在后面。即便你能找到一个可行的次序,但你会被这个次序束缚的很紧而动弹不得。这还算你幸运,因为同样存在你根本找不到一个可行的次序的可能性。更何况,在写代码时,你真正应该全力思考的应该是如何解决问题,而不是把精力浪费在那种对更好地解决问题毫无帮助的鸡毛蒜皮上,那绝对是得不偿失的。因此,第三种方案绝对是贪小便宜吃大亏。
良好编程风格和习惯的意义在于,写代码时不但不用考虑那些对解决问题没有用处的无聊问题,而且不容易出错,即使错了也容易查找和修改。