02.文件组织
一个文件包含的各个部分应该用若干个空行分隔。虽然对源文件没有最大长度限制,但超过1000行的文件处理起来非常不方便。编辑器很可能没有足够的临时空间来编辑这个文件,编译过程也会因此变得十分缓慢。与回滚到前面所花费的时间相比,那些仅仅呈现了极少量信息的多行星号是不值得的,我们不鼓励使用。超过79列的行无法被所有的终端都很好地处理,应该尽可能的避免使用。过长的行会导致过深的缩进,这常常是一种代码组织不善的症状。
2.1 文件命名惯例
文件名由一个基础名、一个可选的句号以及后缀组成。名字的第一个字符应该是一个字母,并且所有字符(除了句号)都应该是小写的字母和数字。基础名应该由八个或更少的字符组成,后缀应该由三个或更少的字符组成(四个,如果你包含句号的话)。这些规则对程序文件以及程序使用和产生的默认文件都适用(例如,"rogue.sav")。
一些编译器和工具要求文件名符合特定的后缀命名约定。下面是后缀命名要求:
- C源文件的名字必须以.c结尾
- 汇编源文件的名字必须以.s结尾
我们普遍遵循以下命名约定:
- 可重定位目标文件名以.o结尾
头文件名以.h结尾
- 在多语言环境中一个可供选择的更好的约定是用语言类型和.h共同作为后缀(例如,"foo.c.h" 或 "foo.ch")。
Yacc源文件名以.y结尾
- Lex源文件名以.l结尾
C++使用编译器相关的后缀约定,包括.c,..c,.cc,.c.c以及.C。由于大多C代码也是C++代码,因此这里并没有一个明确的方案。
此外,我们一般约定使用"Makefile"(而不是"makefile")作为make(对于那些支持make的系统)工具的控制文件,并且使用"README"作为简要描述目录内容或目录树的文件。
2.2 程序文件
下面是一个程序文件各个组成部分的推荐排列顺序:
- 文件的第一部分是一个序,用于说明该文件中的内容是什么。对文件中的对象(无论它们是函数,外部数据声明或定义,或是其他一些东西)用途的描述比一个对象名字列表更加有用。这个序可选择地包含作者信息、修订控制信息以及参考资料等。
- 接下来是所有被包含的头文件。如果某个头文件被包含的理由不是那么显而易见,我们需要通过增加注释说明原因。大多数情况下,类似stdio.h这样的系统头文件应该被放在用户自定义头文件的前面。
- 接下来是那些用于该文件的defines和typedefs。一个常规的顺序是先写常量宏、再写函数宏,最后是typedefs和枚举(enums)定义。
- 接下来是全局(外部)数据声明,通常的顺序如下:外部变量,非静态(non-static)全局变量,静态全局变量。如果一组定义被用于部分特定全局数据(如一个标志字),那么这些定义应该被放在对应数据声明后或嵌入到结构体声明中,并将这些定义缩进到其应用的声明的第一个关键字的下一个层次(译注:实在没有搞懂后面这句的含义)。
- 最后是函数,函数应该以一种有意义的顺序排列。相似的函数应该放在一起。与深度优先(函数定义尽可能在他们的调用者前后)相比,我们应该首选广度优先方法(抽象层次相似的函数放在一起)。这里需要相当多的判断。如果定义大量本质上无关的工具函数,可考虑按字母表顺序排列。
2.3 头文件
头文件是那些在编译之前由C预处理器包含在其他文件中的文件。诸如stdio.h的一些头文件被定义在系统级别,所有使用标准I/O库的程序必须包含它们。头文件还用来包含数据声明和定义,这些数据不止一个程序需要。头文件应该按照功能组织,例如,独立子系统的声明应该放到独立的头文件中。如果一组声明在代码从一种机器移植到另外一种机器时变动的可能性很大,那么这些声明也应该被放在独立的头文件中。
避免私有头文件的名字与标准库头文件的名字一样。下面语句:
#include "math.h"
当预期的头文件在当前目录下没有找到时,它将会包含标准库中的math头文件。如果这的确是你所期望发生的,那么请加上注释。包含头文件时不要使用绝对路径。当从标准位置获取头文件时,请使用<name>
包含头文件;或相对于当前路径定义它们。C编译器的"include-path"选项(在许多系统中为-l)是处理扩展私有库头文件的最好方法,它允许在不改变源码文件的情况下重新组织目录结构。
声明了函数或外部变量的头文件应该被那些定义了这些函数和变量的文件所包含。这样一来,编译器就可以做类型检查了,并且外部声明将总是与定义保持一致。
在头文件中定义变量往往是个糟糕的想法,它经常是一个在文件间对代码进行低劣划分的症状。此外,在一次编译中,像typedef和经过初始化的数据定义无法被编译器看到两次。在一些系统中,重复的没有使用extern关键字修饰的未初始化定义也会导致问题。当头文件嵌套时,会出现重复的声明,这将导致编译失败。
头文件不应该嵌套。一个头文件的序应该描述其使用的其他被包含的头文件的实用特性。在极特殊情况下,当大量头文件需要被包含在多个不同的源文件中时,可以被接受的做法是将公共的头文件包含在一个单独的头文件中。
一个通用的做法是将下面这段代码加入到每个头文件中以防止头文件被意外多次包含。
#ifndef EXAMPLE_H
#define EXAMPLE_H
... /* body of example.h file */
#endif /* EXAMPLE_H */
我们不应该对这种避免多次包含的机制产生依赖,特别是不应该因此而嵌套包含头文件。
2.4 其他文件
还有一个惯例就是编写一个名为"README"的文件,用于描述程序的整体情况以及问题。例如,我们经常在README包含程序所使用的条件编译选项列表以及相关说明,还可以包含机器无关的文件列表等。