前言

  在编写程序的过程中如果不遵守编译器的规定,编译器在编译时就会报错,这个规定叫作规则。但是有一种规定,它是一种约定成俗的规则,即使不按照那种规定也不会出错,这种规定就叫作规范。虽然在编写代码的过程中不遵守规范不会影响代码的运行结果,但会影响自己以及他人阅读代码,从而增加错误排查、理解代码的难度(极端例子可见我之前写的文章)。并且在未来步入职场的时候,不良的代码书写习惯也可能会使你丢掉一个工作。

  代码规范写出来内容很多,可能有些人会抱怨:“这么多怎么记得住?!”。但实际上,代码规范并不需要背记。我们要首先理解代码规范的目的是什么?是让代码更整洁、更易懂。所以有些时候只要能保证代码整洁,就算不严格遵守代码规范依然没有问题。同时这也代表着我们不需要完全将规范背下来,只需要知道一些最基本的内容,然后加以练习、养成习惯,就能自然而然的写出整洁有效的代码。而日后再回顾以前的练习,你也会庆幸自己注意了这方面的内容。


正文

排版

  排版是规范中最为重要的部分,正确的排版可以让代码更加清晰。

缩进

  如同写文章每个自然段前要加两个空格一般,写代码同样需要在一些部分添加空格来对齐代码。比如:

  • 顶层元素不空格
  • 每遇到一个{,下面的代码缩进就要多一段
  • 如果一个字符串被拆成了多行,那么应该使用空格将字符串开头对齐
  • 如果一行代码被拆成了多行,应添加空格表明这几行代码的包含关系

  一段缩进一般为4格或者5格。同时应避免使用制表符(tab)进行缩进,因为使用制表符在不同的编辑器中显示效果可能不同(如果你的IDE会将tab键自动转换为空格进行缩进可以无视这个)。

每个函数前后加空行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int mult(int start, int end) {
unsigned int result = 1;
for (int i = start; i <= end; ++i) {
result *= i;
}
return result;
}

unsigned int cnm(int n, int m) {
if (n- m < m + 1)
return mult(m + 1, n) / mult(1, n - m);
else
return mult(n - m + 1, n) / mult(1, m);
}

同一个函数中功能差别较大的代码块之间添加空行

  比如上面几行代码完成的是一个功能,下面几行代码完成的是另一个功能,那么它们中间就要加空行。这样看起来更清晰。因为C语言的例子我没有找到,这里拿Java写的一个例子来充个数。

集中的变量声明后添加空行

  如果你把函数中的变量全部写到了函数头(数量较多),那么应在声明后添加空行,如果数量不多可以不添加空行。

1
2
3
4
5
6
7
8
9
10
int amount,		//...
year, //...
month, //...
day, //...
min, //...
wula, //...
oula, //...
muda; //...

//do something

善用空格

  除.!~++--、‘(’、‘[’、->等与前(或后)文密切相关的操作符外,所有操作符前后都应添加空格,同时关键字后也应添加空格。

  注意:一个空格足矣,不要连续敲一堆空格(除非你在对齐代码)。

1
2
3
4
5
int sum = a + b;
bool canPrint = sum > 0;
bool continueRun = sum >= 10;
if (canPaint) printf("%d", sum);
if (!continueRun) return false;

一条语句不要过长

  某些情况下我们可能需要写一行非常长的代码(比如包含超长字符串),这时候我们应当将这一行代码拆分为多行,避免代码长度超过屏幕显示宽度。一般情况下代码宽度标准为80,读者也可根据自己屏幕的宽度适当增加这个宽度。

1
2
3
4
5
printf("乌拉乌拉阿斯蒂芬卢卡斯的的规划区内我发噶卫生间而她却尔雅如附件阿斯顿佛为日投标文件拷入按时灯笼裤能否勾起我i而后体哦我石达开你发给i哦请问然后那天给他");
//这里上下两段代码效果是一样的,显然后者更加符合规范
printf("乌拉乌拉阿斯蒂芬卢卡斯的的规划区内我发噶卫生间"
"而她却尔雅如附件阿斯顿佛为日投标文件拷入按时灯笼裤能否"
"勾起我i而后体哦我石达开你发给i哦请问然后那天给他");

一行仅包含一个变量声明(特殊情况例外)

  正常情况下,一行仅包含一条变量声明,这样子也方便为变量添加注释。

1
2
3
4
double pi = 3.1415; //π
int r; //半径
int c; //周长
int s; //面积

控制语句语句独占一行

  正常情况下,if 、for 、 while 、case 、switch 、default 等语句独自占用一行,但如果后续代码非常短也可以共同占用一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//变量赋值这里省略
int min;
bool con;
switch (min) {
case 0:
//do something...
break;
case 1:
for (int i = 0; i < min; ++i) {
//do something...
}
break;
case 2:
while (min != 0) {
//do something...
}
break;
case 3: return 0;
default: return con ? 1 : 2;
}

大括号的位置

  大括号书写的位置有两种写法,读者选择一种使用即可,不要多种混用。

1
2
3
4
5
6
7
//第一种
int main() {
}
//第二种
int main()
{
}

函数

一个函数不要过长

  一个函数的行数尽量维持在50(或80)以下(非空非注释行)。

  但有些函数例外,这些函数可能集成了非常庞大的单个功能,同时由于算法的复杂性无法进行有效地拆分(即拆分可能使代码变得更加复杂难懂)。

重复的代码提取为一个函数

  如果有一个功能在多个地方被使用,那么应该这个功能提炼成一个独立的函数,这样使用这个功能的时候直接调用这个函数就可以了,有效减少了重复代码。

一个函数仅对应一个功能

  一个函数完成多个任务是书写代码的大忌,这样不仅会让函数命名变得复杂,同时会让函数的职责变得不明确,增大了后期维护、修改以及理解的难度。

对参数的合法性检查应有明确的规定

  通常情况下,应当对输入函数的参数进行检查,避免出现意外的情况。但是谁检查是一个普遍存在的问题。有两个极端的情况:一是调用者与被调用者都检查了,二是调用者与被调用者都没有检查。前者虽不会带来运行结果错误,但会出现代码冗余、运行效率降低的情况,后者则可能会导致莫名其妙的问题出现。

  故写代码时应明确规定默认情况下应由谁来对参数进行检查,例外的函数也应在注释中指明情况。

命名规范

标识符的命名规则历来是一个敏感话题,典型的命名风格如unix风格、windows风格等,从来无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改,产品可以根据自己的实际需要指定命名风格,规范中不再做统一的规定。

复制自:https://blog.csdn.net/m0_38106923/article/details/105042594

大分类

一、驼峰式命名

  第一个单词首字母小写,后面其他单词首字母大写(又称小驼峰式)。我在本文列出的所有代码均遵循该命名方式,这里就不单独举例了。

二、帕斯卡命名

  和驼峰式命名类似,只不过将首字母小写改为了首字母大写(故也被称为大驼峰式),也不单独举例。

三、下划线命名

  所有字母小写,每个单词之间使用单下划线隔开。(PS:这个命名方法的名字是我瞎取的。)

1
2
int data_list;
double sum_result;
四、匈牙利命名

  该命名方法不推荐使用,因为其最终使得代码更加难懂,远离了规范的本意,如果想要了解请自行百度。

补充

常量命名方法

  常量(包括枚举常量)命名建议同意采用全部字母大写,单词之间采用单下划线分割的命名格式。这样子有利于一眼识别出该字段指向一个常量。

1
#define PI_ROUNDED 3.1415926
函数命名

  函数命名一般采用 动词 + 名词 的命名方式,因为函数大都是用来执行某一个动作的,没有动作的函数是无意义的。

1
2
3
4
double getPI();
int getData();
int max(int a, int b);
int min(int a, int b);
注意事项

  除了头文件或编译开关等特殊定义,所有常量变量不要用下划线(不论几个)开头,因为下划线开头的常量都是一些内部的定义。

  禁止使用单字节命名变量,但 允许 定义i 、j、k作为局部循环变量

  不建议使用匈牙利命名法

  全局变量应增加g_前缀,静态变量应增加s_

  尽量避免名字中出现数字编号,除非逻辑上的确需要编号

  除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

变量

一个变量对应一个用途

  一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。

尽量使用 const 定义代替宏

  以下内容复制自:https://blog.csdn.net/m0_38106923/article/details/105042594

  “尽量用编译器而不用预处理”,因为#define经常被认为好象不是语言本身的一部分。看下面的语句:

1
#define ASPECT_RATIO 1.653

  编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错,就会很令人费解,因为报错信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
解决这个问题的方案很简单:不用预处理宏,定义一个常量:

1
const double ASPECT_RATIO = 1.653;

表达式

函数调用不要作为另一个函数的参数使用

  不要将一个函数的返回值当作另一个函数的参数,这样子既不利于调试和阅读,也有可能导致意想不到的运算结果,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//该代码摘自:https://blog.csdn.net/m0_38106923/article/details/105042594
int g_var;

int fun1()
{
g_var += 10;
return g_var;
}

int fun2()
{
g_var += 100;
return g_var;
}

int main(int argc, char *argv[], char *envp[])
{
g_var = 1;
printf("func1: %d, func2: %d\n", fun1(), fun2());
g_var = 1;
printf("func2: %d, func1: %d\n", fun2(), fun1());
}

赋值语句不要写在if等语句中

  永远不要将赋值语句写在if中,因为这样会导致后期错误排查时无法明确判断到底是写错了还是故意要在if里面给这个值赋值。

不要过分依赖运算符优先级

  在一段代码中包含多许多运算符时,使用括号表明执行顺序,不要使用优先级确保执行顺序,否则会导致代码阅读困难,也很容易出错。

注释

学会书写优秀的代码

  优秀的代码可以做到自解释,即不需要书写注释也能让人明白代码的含义。

不要过多的使用注释

  注释的本意是帮助人理解代码,过多的注释会让代码变得杂乱无章,起到反作用。

注释的语言

  注释使用何种语言并没有硬性要求,但是考虑到生活环境应该优先使用中文,不要为了撞壁写出自己都读不懂的英文注释。

注释的位置

  注释应放在被注释目标的上方或右方,不要放到下方。

补充

编译器警告

  尽量将编译器警告调到最高级别,这样你更容易发现自己代码的问题。


参考资料:

  按参考程度排序,程度越深越靠前。

  1. 华为C语言编程规范(精华总结)- CSDN
  2. C语言代码规范(编程规范)- C语言中文网
  3. C语言:编码规范 - 知乎
  4. C语言的代码规范有哪些?- 个人图书馆