概述
本文根据自己的日常代码书写习惯,整理一下关于C语言编码过程中的命名规范和注释规范。每个人有自己的编程习惯,本文仅供参考,不作为标准。
正文
由于本人学习安富莱的教材,感觉其编码规范做得很好,所以我们参考安富莱的命名规范来学习。
1、文件与目录
文件命名
文件的命名需要准确清晰,适当使用字母缩写,使得名字精简。如:App.c、Uart.c等。
头文件中段落安排顺序
1、文件头注释
2、防止重复引用头文件的设置
3、#include部分
4、enum常量声明
5、类型声明和定义,包括struct、union、typedef等
6、全局变量声明
7、文件级变量声明
8、全局或文件级函数声明
9、函数实现。按函数声明的顺序排序
10、文件尾注释
在引用头文件时,不要使用绝对路径
#include “/project/inc/hello.h” //不应使用绝对路径
#include “…/inc/hello.h” //可以使用相对路径
在引用头文件时,使用<>还是“”
#include <stdio.h> //标准头文件
#include <projdefs.h> //工程指定目录头文件
#include “global.h” //当前目录头文件
#include “inc/config.h” //路径相对于当前目录的头文件
防止头文件被重复引用
#ifndef __DISP_H //文件名前加两个下划线,后面加_H
#define __DISP_H
…
…
#endif
头文件中只存放“声明”而不存放“定义”
/* 模块1头文件:module1.h */
extern int a = 5; //在模块1的.h文件中声明变量
文件的长度
文件的长度没有非常严格的要求,但应尽量避免文件过长。一般来说,文件长度应尽量保持在1000行以内。
2、排版
(1)程序块要采用缩进风格编写,缩进的空格数为4个。
(2)相对独立的程序块之间、变量说明之后必须加空行。
(3)较长的语句或函数过程参数(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
(4)不允许把多个短语句写在一行中,即一行只写一条语句。
(5)程序块的分界符(如大括号)应各独占一行并且位于同一列。
(6)在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如- >),后不应加空格。
uint32_t a, b, c;
if (current_time >= MAX_TIME_VALUE)
{
a = b + c;
a *= 2;
a = b ^ 2;
}
*p = 'a';
flag = !isEmpty;
p = &mem;
i++;
p->id = pid;
if (a >= b && c > d)
3、注释
常见在函数、模块的头部注释或者同行注释。
常见的注释说明如下:
@brief 函数、模块功能简要说明
@remark 特殊说明
@param[in] 输入参数
@param[out] 输出参数
@return 返回类型
@retval 返回值
…
4、可读性
(1)注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
/*正确的写法*/
word = (high << 8) | low;
if ((a | b) && (a & c))
if ((a | b) < (c & d))
/*错误的写法*/
word = high << 8 | low;
if (a | b && a & c)
if (a | b < c & d) //造成了判断条件出错
(2)避免使用不易理解的数字,用有意义的标识来替代。
/*不规范写法*/
if (Trunk[index].trunk_state == 0) //<---不规范写法,应使用有意义的标识
{
Trunk[index].trunk_state = 1; //<---不规范写法,应使用有意义的标识
}
/*规范写法*/
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
}
(3)不要使用难懂的技巧性很高的语句,除非很有必要时。
说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。
示例:如下表达式,考虑不周就可能出问题,也较难理解。
/*不规范代码*/
* stat_poi ++ += 1;
* ++ stat_poi += 1;
/*规范代码*/
*stat_poi += 1;
stat_poi++; //此二语句功能相当于“* stat_poi ++ += 1;”
++ stat_poi;
*stat_poi += 1; //此二语句功能相当于“* ++ stat_poi += 1;”
5、变量、结构、常量、宏
(1)为方便书写及记忆,变量类型采用如下重定义:
(2)常见类型的前缀
(3)变量作用域的前缀
为了清晰的标识变量的作用域,减少发生命名冲突,应该在变量类型前缀之前再加上表示变量作用域的前缀,并在变量类型前缀和变量作用域前缀之间用下划线隔开。
具体的规则如下:
uint32_t g_ulParaaWord;
uint8_t g_ucByte;
static uint32_t s_ulParaWord;
static uint8_t s_ucByte;
(4)结构体命名规则
表示类型的名字,所有名字以小写字母tag开头,之后每个英文单词的第一个字母大写(包括第一个单词的第一个字母),其他字母小写,结尾T标识。单词之间不使用下划线分隔,结构体变量以t开头。如:
typedef struct tagBillQuery_T
{
...
}BillQuery_T;
/*结构体变量定义*/
BillQuery_T tBillQuery;
(5)对于枚举定义全部采用大写,结尾_E标识。
typedef enum
{
KB_F1 = 0, //F1键代码
KB_F2, //F2键代码
KB_F3 //F3键代码
}KEY_CODE_E;
(6)常量、宏、模版的名字应该全部大写。如果这些名字由多个单词组成,则单词之间用下划线分隔。
#define LOG_BUF_SIZE 8000
6、函数
(1)函数的命名规则
每一个函数名前缀需包含模块名,模块名为小写,与函数名区别开。
如:uartReceive(串口接收)
备注:对于非常简单的程序,可以不加模块名。
(2)函数的形参
函数的形参都以下划线开头,已示与普通变量进行区分,对于没有形参为空的函数(void)括号紧跟函数后面。
(3)一个函数仅完成一件功能。
(4)函数名应准确描述函数的功能,使用动宾词组为执行某操作的函数命名。
说明:避免用含义不清的动词如process、handle等为函数命名,因为这些动词并没有说明要具体做什么。
示例:参照如下方式命名函数。
void PrintRecord(uint32_t _RecInd);
int32_t InputRecord(void);
uint8_t GetCurrentColor(void);
(5)避免设计五个以上参数函数,不使用的参数从接口中去掉。
说明:目的减少函数间接口的复杂度,复杂的参数可以使用结构传递。
(6)在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转换。
说明:因为数据类型转换或多或少存在危险。
(7)防止把没有关联的语句放到一个函数中。
示例:如下函数就是一种随机内聚。
void InitVar(void)
{
Rect.length = 0;
Rect.width = 0; //初始化矩形的长与宽
Point.x = 10;
Point.y = 10; //初始化“点”的坐标
}
矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。应如下分为两个函数:
void InitRect(void)
{
Rect.length = 0;
Rect.width = 0; //初始化矩形的长与宽
}
void InitPoint(void)
{
Point.x = 10;
Point.y = 10; //初始化“点”的坐标
}
参考
- 安富莱电子论坛