C高质量编码规范
一、编码规范的介绍
编码规范的重要性
- 程序员的态度:不愿意测试自己的代码;不愿意review团队队员的代码
- 专业的程序员:写出人能读懂的代码。程序首先是给人读的,其次才是给机器读的。
程序员自身在程序开发中的流程
- 仔细设计、编写代码、单元测试、功能测试、代码review
二、宏观上高质量编码规范
1.版权和版本的申明
版权和版本的申明位于头文件和定义文件的开头,如下:
- 版权信息
- 文件名称、标识符、摘要
- 当前版本号,作者/修改者,完成日期
- 版本历史信息
/*Copyright(c)2001,company
*All rights reserved.
*文件名称:filename.h
*文件标识:见配置管理计划书
*摘要:简述本文件的内容
*当前版本:1.1
*作者:作者(或修改者)名字
*完成日期:2021年4月5日
*取代版本:1.0
*原作者:作者(或修改者)名字
*完成日期:2021年4月1日
*/
2.头文件的结构
头文件由三部分内容组成
- 头文件开头处的版权和版本声明
- 预处理块
- 函数和结构体声明等
使用注意点
- 为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块
- 用#include<filename.h>格式来引用标准库的头文件,用#include"filename.h"格式来引用非标准库的头文件。
- 头文件中只存放“声明”而不存放“定义”。
- 头文件中不提倡使用全局变量,尽量不要在头文件中出现像extern int value这类声明
3.源文件的结构
源文件由三部分内容组成
- 定义文件开头处的版权
- 对一些头文件的引用
- 程序的实现体
4.工程目录结构
特点
- 便于维护:将头文件和定义文件分别保存于不同的目录
- 加强信息隐藏:如果某些头文件是私有的,他不会被用户的程序直接引用,则没有必要公开其“声明”。
示例
Network工程建立三个目录:
目录名 | 存放文件 | 文件格式 |
---|---|---|
source | 存放工程源文件 | server.c,client.c |
include | 存放工程头文件 | server.h,client.h |
lib | 存放工程库文件 | tipr.so,stdio.so |
5.命名规则
主要思想
- 在变量和函数名中加入前缀以增进人们对程序的理解
具体规则 - 标识符应当直观且可读,可望文生意
- 标识符长度应当符合“min-length&&max-information”原则
- 程序中不要出现仅靠大小写区分的相似的标识符
- 程序中不要出现标识符完全相同的局部变量和全局变量
- 变量的名字应当使用“名词”或者“形容词+名词”
- 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
6.程序的版式
空行
- 空行起着分隔程序段落的作用。空行得体将使程序布局更加清晰。空行不会浪费内存。
- 空行规则
- 每个函数定义结束后都要加空行
- 在一个函数体内,逻辑上密切相关的语句之间不加空行,其他地方应加空行分隔。
代码行
- 一行代码只做一件事情:如只定义一个变量或只写一个语句。这样代码易读,且便于写注释
- if,for,while,do等语句自占一行,执行语句后不得紧跟其后。不论执行语句有多少都要加{}。
代码行内的空格
- 关键字之后要留空格
- 函数名之后不要留空格,紧跟左括号,以与关键字区别
- 二元运算符前后应加空格,一元运算符前后不加空格
- “[]” “.” "->"这类运算符前后不加空格
- 逗号之后加空格
代码对齐
- 程序的分界符’{‘和’}'应独占一行并且位于同一列,同时与引用他们的语句左对齐
- {}之内的代码块在’{'右边数格处左对齐
长行拆分
- 代码行最大长度宜控制在70到80个字符以内
- 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)
- 拆分出的新行要进行适当的缩进,使排版整齐,语句可读
修饰符的位置
- 修饰符*和&
- 修饰符紧靠变量名,如:int *x, y; //此处y不会被误认为是指针
注释
- C语言的注释符为多行注释"/…/“和单行注释”//…"
- 注释通常用于
- 版本、版权声明
- 函数接口说明
- 重要的代码行或段落提示
- 示例
/*
*函数介绍:
*输入参数:
*输出参数:
*返回值:
*/
注释规则
- 注释是对代码的"提示",而不是文档
- 如果代码本来就是清楚的,则不必注释
- 边写代码边注释,边调试边注释
- 注释的位置应与被描述的代码相邻,上面或右侧注释
- 当代码比较长,应当在段落结束处加注释
三、微观上高质量编码规范
1.程序的健壮性
(1)使用断言
- 程序一般分为Debug版本和Release版本,断言assert是仅在Debug版本起作用的宏
断言优势 - 跟踪程序运行,帮助调试
- 输出错误原因
- 可以自定义
使用断言规则 - 使用断言捕捉不应该发生的非法情况
- 在函数入口处,使用断言检查参数的有效性
- 一旦确定了的假定,就要使用断言对假定进行检查
- 如果“不可能发生”的事情的确发生了,则要使用断言进行报警
/*assert的使用*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
void out(FILE *spIn)
{
assert(spIn != NULL); //对输入参数进行控制
}
int main(void)
{
FILE *spIn;
spIn = fopen("src/a.txt","r");
assert(spIn != NULL); //是一个带参宏,不是函数,输出一些错误信息并终止运行
spIn = NULL;
out(spIn);
return 0;
}
(2)复合表达式
- 示例:a=b=c=0;
- 优势:书写简洁,提高编译效率
- 复合表达式使用规则
- 不要编写太复杂的复合表达式,如:i=a>=b&&c<d&&c+f<=g+h;
- 不要有多用途的复合表达式。如:d=(a=b+c)+r;
- 不要把程序中的复合表达式与“真正的数学表达式”混淆,如:if(a<b<c)与if(a<b&&b<c)
(3)if语句
-
布尔变量与零值比较
- 不可将布尔变量直接与TRUE、FALSE或者1,0进行比较。建议采用逻辑非运算符进行比较。
如:if(flag) 表示flag为真。 if(!flag) 表示flag为假
- 不可将布尔变量直接与TRUE、FALSE或者1,0进行比较。建议采用逻辑非运算符进行比较。
-
整型变量与零值比较
- 整型变量用"==“或”!="直接与0比较
-
浮点变量与零值比较
- 不可将浮点变量用"==“或”!="与任何数字比较
- 设法转化成">=“或”<="形式
-
指针变量与零值比较
- 指针变量用"==“或”!="与NULL比较
(4)使用const提高函数的健壮性
- 定义只读变量
- 修饰函数的参数
- 如果输入参数采用"指针传递",那么加const修饰可以防止意外地通过该指针去修改它所指向的数据(常量指针),起到保护作用。
如:void string_copy(char *str_dest, const char *str_src); //可以输入非常量指针
- 如果输入参数采用"指针传递",那么加const修饰可以防止意外地通过该指针去修改它所指向的数据(常量指针),起到保护作用。
- 函数返回值加const修饰
- 如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)所指向的数据不能被修改
如:const char *get_string(void);
出现编译错误:char *str = get_string();
正确的用法是:const char *str = get_string();
- 如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)所指向的数据不能被修改
(5)内存管理规则
- 用malloc之后,应该立即检查指针值是否为NULL
(6)指针参数的内存传递
- 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。
void get_mem(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
}//p指针在函数调用完后就消失了,而内存空间还在,没有释放
void test(void)
{
char *str = NULL;
get_mem(str,100); //调用后,str仍为NULL
strcpy(str, "hello"); //运行错误 段错误
}
- 不要用return语句返回指向"栈内存"的指针
char *get_str(void)
{
char p[]="hello world";
return p; //编译器将提出警告
}
void test(void)
{
char *str = NULL;
str = get_str(); //str的内容是垃圾
printf("%s\n",str);
}
2.程序的优化
常量使用
- 需要对外公开的常量放在头文件中
循环语句的效率
- 将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数
- 如果循环体内存在逻辑判断,并且循环次数很大,应将逻辑判断移到循环体的外面
for语句的循环控制变量
- 不可在for循环体内修改循环变量,防止for循环失去控制
- 建议for语句的循环控制变量的取值采用"半开半闭区间"写法
如:for(int i=0;i<n;i++)