跳转到内容

1. 缩进和空白

我们知道 C 语言的语法对缩进和空白没有要求,空格、Tab、换行都可以随意写,实现同样功能的代码可以写得很好看,也可以写得很难看。例如上一章 例 8.5“剪刀石头布” 的代码如果写成这样就很难看了:

例 9.1. 缺少缩进和空白的代码

c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
char gesture[3][10]={"scissor","stone","cloth"};
int man,computer,result, ret;
srand(time(NULL));
while(1){
computer=rand()%3;
printf("\nInput your gesture (0-scissor 1-stone 2-cloth):\n");
ret=scanf("%d",&man);
if(ret!=1||man<0||man>2){
printf("Invalid input! Please input 0, 1 or 2.\n");
continue;
}
printf("Your gesture: %s\tComputer's gesture: %s\n",gesture[man],gesture[computer]);
result=(man-computer+4)%3-1;
if(result>0)printf("You win!\n");
else if(result==0)printf("Draw!\n");
else printf("You lose!\n");
}
return 0;
}

一是缺少空白字符,代码密度太大,看着很费劲。二是没有缩进,看不出来哪个{和哪个}配对,像这么短的代码还能凑合着看,如果代码超过一屏就完全没法看了。CodingStyle 中关于空白字符并没有特别规定,因为基本上所有的 C 代码风格对于空白字符的规定都差不多,主要有以下几条。

  1. 关键字 ifwhilefor 与其后的控制表达式的 ( 括号之间插入一个空格分隔,但括号内的表达式应紧贴括号。例如:

    c
    while(1);
  2. 双目运算符的两侧各插入一个空格分隔,单目运算符和操作数之间不加空格,例如 i␣=␣i␣+␣1++i!(i␣<␣1)-x&a[1] 等。

  3. 后缀运算符和操作数之间也不加空格,例如取结构体成员 s.a 、函数调用 foo(arg1) 、取数组成员 a[i]

  4. , 号和 ; 号之后要加空格,这是英文的书写习惯,例如 for␣(i␣=␣1;␣i␣<␣10;␣i++)foo(arg1,␣arg2)

  5. 以上关于双目运算符和后缀运算符的规则并没有严格要求,有时候为了突出优先级也可以写得更紧凑一些,例如 for␣(i=1;␣i<10;␣i++)distance␣=␣sqrt(x*x␣+␣y*y) 等。但是省略的空格一定不要误导了读代码的人,例如 a||b␣&&␣c 很容易让人理解成错误的优先级。

  6. 由于 UNIX 系统标准的字符终端是 24 行 80 列的,接近或大于 80 个字符的较长语句要折行写,折行后用空格和上面的表达式或参数对齐,例如:

    c
    if(sqrt(x*x␣+␣y*y)>5.0
        &&␣x␣<0.0
        &&␣y␣>0.0)

    再比如:

    c
    foo(sqrt(x*x␣+␣y*y),
        a[i-1]+b[i-1]+c[i-1])
  7. 较长的字符串可以断成多个字符串然后分行书写,例如:

    c
    printf("This is such a long sentence that "
           "it cannot be held within a line\n");

    C 编译器会自动把相邻的多个字符串接在一起,以上两个字符串相当于一个字符串 "This is such a long sentence that it cannot be held within a line\n"

  8. 有的人喜欢在变量定义语句中用 Tab 字符,使变量名对齐,这样看起来很美观。

    c
    int    →a, b;
    double →c;

内核代码风格关于缩进的规则有以下几条。

  1. 要用缩进体现出语句块的层次关系,使用 Tab 字符缩进,不能用空格代替 Tab。在标准的字符终端上一个 Tab 看起来是 8 个空格的宽度,如果你的文本编辑器可以设置 Tab 的显示宽度是几个空格,建议也设成 8,这样大的缩进使代码看起来非常清晰。如果有的行用空格做缩进,有的行用 Tab 做缩进,甚至空格和 Tab 混用,那么一旦改变了文本编辑器的 Tab 显示宽度就会看起来非常混乱,所以内核代码风格规定只能用 Tab 做缩进,不能用空格代替 Tab。

  2. if/elsewhiledo/whileforswitch 这些可以带语句块的语句,语句块的 {} 应该和关键字写在同一行,用空格隔开,而不是单独占一行。例如应该这样写:

    c
    if(...){
           →语句列表
    }elseif(...){
           →语句列表
    }

    但很多人习惯这样写:

    c
    if(...)
    {
           →语句列表
    }
    elseif(...)
    {
           →语句列表
    }

    内核的写法和 K&R 一致,好处是不必占太多行,使得一屏能显示更多代码。这两种写法用得都很广泛,只要在同一个项目中能保持统一就可以了。

  3. 函数定义的 {} 单独占一行,这一点和语句块的规定不同,例如:

    c
    intfoo(int␣a,int␣b)
    {
           →语句列表
    }
  4. switch 和语句块里的 casedefault 对齐写,也就是说语句块里的 casedefault 标号相对于 switch 不往里缩进,但标号下的语句要往里缩进。例如:

    c
    switch(c){
    case 'A':
          →       →语句列表
    case 'B':
          →       →语句列表
    default:
          →       →语句列表
    }

    用于 goto 语句的自定义标号应该顶头写不缩进,而不管标号下的语句缩进到第几层。

  5. 代码中每个逻辑段落之间应该用一个空行分隔开。例如每个函数定义之间应该插入一个空行,头文件、全局变量定义和函数定义之间也应该插入空行,例如:

    c
    #include <stdio.h>
    #include <stdlib.h>
    
    int g;
    double h;
    
    int foo(void)
    {
           →语句列表
    }
    
    int bar(int a)
    {
           →语句列表
    }
    
    int main(void)
    {
           →语句列表
    }
  6. 一个函数的语句列表如果很长,也可以根据相关性分成若干组,用空行分隔。这条规定不是严格要求,通常把变量定义组成一组,后面加空行, return 语句之前加空行,例如:

    c
    int main(void)
    {
    int    →a, b;
    double →c;
    
           →语句组 1
    
           →语句组 2
    
    return 0;
    }