结构与风格
C语言的结构与风格
这是对 C 编程语言中良好编码风格的基础介绍。它旨在提供如何有效使用缩进、注释和其他元素的信息,这些元素将使你的 C 代码更具可读性。它不是关于实际 C 编程的教程。

作为初学者,代码中结构化的意义可能不太清楚,因为编译器并不关心这些差异。然而,随着程序的复杂化,编写程序可能变成了多人合作的工作。(或者别人可能希望查看你的实现过程,或者你几年后可能需要重新阅读它。)编写良好的代码也有助于你快速了解代码的功能。

在接下来的部分中,我们将尝试解释一些好的编程实践,这些实践将使你的程序更加清晰。

介绍

在 C 中,程序由语句组成。语句以分号结尾,并被组织成称为函数的代码块。根据约定,语句应该单独占一行,如下所示:

#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

以下的代码块本质上与前面的代码相同。尽管它包含完全相同的代码,并且会编译并执行相同的结果,但由于删除了空格,导致本质上的差异:它更难以阅读。

#include <stdio.h>
int main(void) {printf("Hello, World!\n");return 0;}

简单地使用缩进和换行可以大大提高代码的可读性,而不会影响代码性能。可读的代码让我们更容易看出哪些函数和程序结束,哪些行属于哪个循环或过程。

本节将专注于改善一个示例代码的编码风格,该代码应用一个公式并打印结果。稍后,你将看到如何更详细地编写处理此类任务的代码。现在,重点关注代码的外观,而不是它的功能。

换行与缩进

在代码中添加空白字符可以说是良好代码结构中最重要的部分。有效地使用空白字符可以创建一种可视化的代码流,特别是在你回到代码并需要维护时,这一点非常重要。

换行

注意行号。它们不是实际代码的一部分,仅供参考。

如果没有换行,代码几乎无法阅读,并且可能难以调试或理解:

#include <stdio.h>
int main(void) { int revenue = 80; int cost = 50; int roi; roi = (100 * (revenue - cost)) / cost; if (roi >= 0) { printf ("%d\n", roi); } return 0; }

与将所有内容放在一行上相比,将长行拆分成每个语句和声明单独占一行会使代码更易读。插入换行符后,代码将如下所示:

#include <stdio.h>
int main(void) {
    int revenue = 80;
    int cost = 50;
    int roi;
    roi = (100 * (revenue - cost)) / cost;
    if (roi >= 0) {
        printf ("%d\n", roi);
    }
    return 0;
}

空行

空行应当用于区分代码的主要部分。始终使用它们:

  • 在预处理器指令后。
  • 在新变量声明后。
  • 根据个人判断,找到其他应该分隔的部分。

根据这两条规则,应该至少添加两个换行:

  • 在第 1 行后,因为第 1 行包含预处理指令。
  • 在第 5 行后,因为第 5 行包含变量声明。

这样,代码的可读性比之前提高了很多:

以下是包含函数之间换行的代码行,但缺少缩进:

#include <stdio.h>

int main(void) {

    int revenue = 80;
    int cost = 50;
    int roi;

    roi = (100 * (revenue - cost)) / cost;

    if (roi >= 0) {
        printf ("%d\n", roi);
    }

    return 0;
}

但是,它仍然没有达到最优的可读性。

缩进
许多文本编辑器在你按下回车键时会自动进行适当的缩进。

虽然在关键代码块之间添加简单的换行可以让代码更易于阅读,但它并没有提供程序的块结构信息。使用 Tab 键可以非常有帮助。缩进通过将执行路径的起始点移到新的列中,直观地分隔不同的执行路径。这个简单的做法将使代码更易读和理解。缩进遵循一个相当简单的规则:

  • 所有新块中的代码应该比前一条路径的代码缩进一个 Tab 键。

根据上一节的代码,有两个块需要缩进:

  • 第 4 行到第 16 行
  • 第 13 行
#include <stdio.h>

int main(void) {

    int revenue = 80;
    int cost = 50;

    int roi;

    roi = (100 * (revenue - cost)) / cost;

    if (roi >= 0) {
        printf ("%d\n", roi);
    }

    return 0;
}

现在,程序中的哪些部分属于哪个代码块已经非常清晰。你可以看出程序员意图将哪些部分作为条件语句,哪些不是。尽管一开始可能不容易察觉,一旦程序结构中加入了多个嵌套路径,使用缩进就显得尤为重要。因此,缩进使得程序的结构更加清晰。

研究表明,缩进大小为 2 到 4 个字符的代码比 8 个字符缩进更容易阅读[2]。然而,某些系统可能仍然使用 8 个字符的缩进[3]。

注释

代码中的注释在多种场合下都非常有用。它们提供了一种最简单的方式来标注代码的特定部分(以及它们的目的),同时在代码的不同部分之间提供视觉上的“分隔”。在代码中拥有良好的注释会让你更容易记住各个部分的功能。

在现代 C 语言(以及许多其他语言)中,注释有两种形式:

  • // 单行注释(由 C99 标准引入,著名的 C++ 风格注释)
  • /* 多行注释(仅 C89 标准支持的注释形式)

请注意,单行注释是 C 语言的一个较新特性,因此某些编译器可能不支持它们。较新的 GCC 版本将不受影响。

本节将重点介绍每种注释形式的不同用途。

单行注释

单行注释最适合用于解释代码的简单“附加”说明。最好的位置是放在变量声明旁边,或者是放在需要解释的代码段旁边。注释应该明确表达出相应代码的意图和思想。代码中显而易见的内容不应出现在注释中。

基于我们之前的程序,以下是几个适合添加注释的地方:

  • 第 5 行和/或第 6 行,用于解释 int revenueint cost 的含义。
  • 第 8 行,用于解释变量 roi 将如何使用。
  • 第 10 行,用于解释计算公式的思想。
  • 第 12 行,用于解释 if 语句的目的。

这将使我们的程序变得更加清晰:

#include <stdio.h>

int main(void) {

    int revenue = 80;               // 2016年的收入
    int cost = 50;                  // 成本

    int roi;                        // 投资回报率(百分比)

    roi = (100 * (revenue - cost)) / cost;  // 会计书中的计算公式

    if (roi >= 0) {                 // 我们不关心负的投资回报率
        printf ("%d\n", roi);
    }

    return 0;
}

多行注释

单行注释是一个新特性,因此许多 C 程序员仍然只使用多行注释。

多行注释最适合用于对代码进行长篇解释。它们可以用于版权/许可声明,也可以用于解释某个代码块的目的。这有两个好处:它们使函数更容易理解,同时也使在代码中发现错误变得更加容易。如果你知道一个代码块应该做什么,那么在出现错误时就能更容易找到负责的代码部分。

举个例子,假设我们有一个程序,设计用来打印“Hello, World!”若干行,每行打印指定次数。在这个程序中将有许多 for 循环。为了说明,我们将行数设为 i,每行的字符串数设为 j

描述 for 循环 i 目的的多行注释示例如下:

/* For Loop (int i)
   循环以下过程 i 次(代表行数)。在每次循环中,执行 'for' 循环 j 次,并在每次循环结束时打印一行。
*/

这很好地解释了 i 的目的,同时没有过多详细描述 j 做了什么。通过只详细解释具体路径(而不是其中的部分),调试路径时会更容易。

类似地,你应该始终在每个函数前添加多行注释,解释每个函数的作用、前置条件和后置条件。始终将技术细节留给程序内部的具体代码块——这有助于调试。

一个函数描述符应该看起来像这样:

/* Function : int hworld (int i, int j)
   Input    : int i (行数), int j (每行实例数)
   Output   : 0(成功时)
   Procedure: 打印 "Hello, World!" j 次,并在 i 行中打印每行一个新行。
*/

这种方式允许你一目了然地了解函数的作用,稍后可以详细描述程序如何实现每个方面。

最后,如果你喜欢美观的源代码,使用多行注释系统可以轻松地添加注释框。这些框会让注释更加突出,通常看起来像这样:

/***************************************
 *  这是一个多行注释
 *  几乎被一个酷炫的星星边框包围!
 ***************************************/

应用到我们原来的程序,代码现在可以包括更具描述性和可读性的源代码:

#include <stdio.h>

int main(void) {
    /************************************************************************************
     * Function: int main(void)
     * Input   : 无
     * Output  : 成功时返回 0
     * Procedure: 打印 2016 年的投资回报率(百分比),如果它不为负数。
     ************************************************************************************/
    int revenue = 80;               // 2016年的收入
    int cost = 50;

    int roi;                        // 投资回报率(百分比)

    roi = (100 * (revenue - cost)) / cost;  // 会计书中的计算公式

    if (roi >= 0) {                 // 我们不关心负的投资回报率
        printf ("%d\n", roi);
    }

    return 0;
}

这将使程序的外部用户更容易理解代码的功能及其操作方式,同时避免了对其他同名函数的混淆。

一些程序员会在块注释的右侧添加一列星星:

/***************************************
 *  这是一个多行注释                   *
 *  完全被一个酷炫的星星边框围绕!     *
 ***************************************/

但大多数程序员并不会在块注释的右侧添加任何星星,他们认为对齐右侧是一种浪费时间的做法。

源代码文件中的注释可以通过使用像 Doxygen 这样的流行工具自动生成源代码文档。[4][5]

参考

一些程序员推荐“使用空格进行缩进,不要在代码中使用制表符。你应该将编辑器设置为在按下 Tab 键时使用空格。”[1][2] 另一些程序员对此意见相左。[3][4] 无论你更倾向于使用空格还是 Tab 键,都应该确保在工作中的项目中保持一致。混用 Tab 和空格会使代码变得难以阅读。
链接1 Vim 烹饪书
链接2 Linux 内核编码风格
“C++ 和 Java 编程规范” “本文档中所有的块注释在右侧没有漂亮的星星。这个故意的选择是因为对齐这些漂亮的星星是一种巨大的时间浪费,并且不利于内联注释的维护。”
c2:BigBlocksOfAsterisks, "Code craft" by Pete Goodliffe page 82, Falvotech "C 编程风格指南", Fedora 目录服务器编码风格
Aladdin 的 C 编程指南 - 一个更权威的 C 编程指南。
C/C++ 编程风格

最后修改: 2025年01月12日 星期日 13:22