文章阐述了一些个人使用 C 编程时的习惯,主要参考一些网传的文档,固件库以及多个工程。

命名规则

结构体类型和函数的标识符一般用大驼峰式书写格式,普通变量和结构体变量的标识符则多用小驼峰式书写格式,但在此基础之上进行一定的改进不完全沿用。

宏定义的命名全部采用大写,单词之间用隔开,要求常量名只含有大写字母和下划线,且常量名用英文表达其意思不得使用拼音,当需要由多个单词表示时,单词与单词之间必须采用连字符”“连接。

1
2
如:
#define ERR_FILE_NOT_FOUND

全局变量、静态变量、常量尽可能的详细描述,普通局部变量和成员变量尽可能的书写简单;变量的描述都普遍按照小驼峰的方式。

这种命名法的出发点是把变量名按:属性+类型+对象描述的顺序组合起来:

  • 硬件相关部分:

由于 C 语言很多时候面向硬件开发,这边规定,硬件相关的名称全部大写,后面用“_”隔开,如:

  • 属性部分:
1
2
3
g_ :全局变量,如一个全局的长型变量定义为 g_lFailCount
s_ :静态变量,如一个静态的指针变量定义为 s_plPerv_Inst
c_ :常量,const 的变量 示例:const char* c_szFileName
  • 类型部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool 用b开头 b标志寄存器 
int 用 i 32位开头 iCount 
long int 用l开头 lSum 
char  用c开头 cCount 
float 用f开头 fAvg 
double 用d开头 dDeta 
unsigned char / byte(字节) 用by开头 
unsigned int(WORD) 用w开头 wCount 
unsigned long int(DWORD) 用dw开头 dwBroad 
字符串 用s开头 sFileName 
注意:以上所有类型只有在和属性部分(g_,s_,c_等)一起使用。
数组 arr:如一个 int 型数组应该表示为 arriStat。
指针 p:pData
对一重指针变量的基本原则为:“p”+变量类型前缀+命名,如一个float*型应该表示为pfStat。对二重指针变量的基本规则为:“pp”+变量类型前缀+命名。对三重指针变量的基本规则为:“ppp”+变量类型前缀+命名。 
注意:虽然上面的基本类型不在局部变量中沿用,但是数组和指针类型在局部变量中也要使用,但不加入其他基本类型的符号。

普通局部变量,尽量做到简洁明了,毕竟函数较短,能够轻松查看原型,因此这边一般都采用直接小写的方式,尽量通过一个单词来表述意思,只有在用到指针和数组时才会加入前缀,且后面命名用大写,如:arrMember, pScore 等等。

  • 描述部分:
1
2
最大 Max 最小 Min 初始化 Init 临时变量 T(或Temp)
源对象 Src 目的对象 Dest

枚举型命名类型名采用驼峰命名法,前面加EM前缀,其中的变量采用 EM_+ 大写字母类似宏定义的形式,申明为枚举型的变量使用以 em 为头的驼峰式命名(枚举变量本身就类似于常量)。

1
2
3
4
5
6
7
8
如: 
enum EMDays 
EM_DAYS_MONDAY; 
EM_DAYS_TUESDAY; 
…… 
}; 
EMDays  emToday;

结构体、类、共用体的命名规则统一采用大驼峰式命名法,结构体类型定义时在前面需要加上 Stru 前缀,结构体变量初始化申明的时候直接采用大驼峰式即可,其成员变量采用小驼峰式命名和普通变量一致。(函数与结构体变量方便区分,固都采用大驼峰式)

1
2
3
4
5
6
7
如:
typedef struct
char *pNodeNext;
char *pNodePreview;
}StruStudentNode; 
StruStudentNode StudentNode;

函数的命名规则一般采用大驼峰式,命名采用“主动宾”或者“动宾”结构,与结构体变量虽然同样采用大驼峰式,但通过使用方法和命名方便进行区分。同时部分特殊函数可以采用其他命名规则,如NVIC_Configuration,I2C_init,delay_us(硬件控制,非软件延时) 硬件设备相关的使用下划线,以此来区分哪些是特殊函数(函数与结构体方便区分采用大驼峰式)

函数参数命名规范 :

  • 参数名称的命名参照变量命名规范。
  • 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。
  • 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。
1
2
3
4
5
如:
int CmCopyString(const char * c_pcSource, char* pcDest) 
{
...
}
  • 普通函数与中断调用函数区别命名,普通函数如果需要设定控制终中断函数则命名以Set开头通过修改中断函数中的全局变量判定条件来控制中断执行,而中断功能函数只能被中断调用,不可直接被普通函数调用(包括主函数),命名以 Run 开头。

硬件设备名称函数命名参考 stm32 固件库命名法则:外设名称在前面全部大写

1
2
3
4
5
6
7
8
9
如:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

除了编译开关/头文件等特殊应用,应避免使用 _EXAMPLETEST 之类以下划线开始和结尾的定义。

文件名

文件名全部使用小写字母、下划线和数字,多个具体含义的单词中用_隔开,不得出现大写字母等任何其他字符。

  • Linux 系统是大小写敏感的,如果文件名只有大小写不同,则跨平台就会出问题。
  • 小写文件名通常比大写文件名更易读,比如 accessibility.txt 就比 ACCESSIBILITY.TXT 易读。
  • 某些系统会生成一些预置的用户目录,采用首字母大写的目录名。比如,Ubuntu 在用户主目录会默认生成Downloads、 Pictures、Documents等目录。
  • 文件名全部小写,还有利于命令行操作。比如,某些命令可以不使用-i参数了。

命名具备意义

文件名(包括动态库、组件、控件、工程文件等)的命名规范,文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象 file1,myfile 之类的文件名。

注释规范

文件注释:(使用 /*...*/ 整体注释,不得采用 // 行注释)

每个文件,都必须具备文件注释,可以不完全具备下面的内容,但至少要具备:文件名、版权、简述。

详细格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/********************* Copyright(C) 20xx company********************
File name: // 文件名
Author: // 作者、
Version: // 版本
Date: // 完成日期
Description: // 用于详细说明此程序文件完成的主要功能,与其他模块
// 或函数的接口,输出值、取值范围、含义及参数间的控
// 制、顺序、独立或依赖等关系
License: // 版权协议
Others: // 其它内容的说明
Function List: // 主要函数列表,每条记录应包括函数名及功能简要说明
1. ....
History: // 修改历史记录列表,每条修改记录应包括修改日期、修改
// 者及修改内容简述
<author> <time> <version > <desc>
Name1 20xx.x.x Vx.x //修改的内容
*************************************************/

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/************************************ Copyright (C) 2015 HS ***********************************
File name: channel.c
Author: name
version: V1.0
Description: 实现多个通道的脉冲发送
Others:
Function List:
void GetChanCtrl(void);
void SetNormalMode(void);
void SetImMode(void);
void CtrlPulse(u8 g_byComRev);
void SetTimeMode(void);
u16 ScanfValue(void);
u32 ScanfTime(void);
void CtrlChannel(void);
void InitChannel(void);
void RunNormal(void);
void RunPulse(void);
void RunMode(void);
History:
<author> <time> <version > <desc>
Name1 2015.6.1 V1.1 修复xx问题
**************************************************************************************************/

函数注释:(使用 /*...*/ 整体注释,不得采用 // 行注释)

要求每个函数都需要有注释,但格式不做强制要求,功能复杂的函数必须具备:函数描述、参数说明、返回值;较为简短的函数可以简述功能。

以下为一个比较详细的格式说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**************************************************************************
Function: // 函数名称
Description: // 函数功能、性能等的描述
Calls: // 被本函数调用的函数清单
Called By: // 调用本函数的函数清单
Table Accessed: // 被访问的表(此项仅对于牵扯到数据库操作的程序)
Table Updated: // 被修改的表(此项仅对于牵扯到数据库操作的程序)
Input: // 输入参数说明,包括每个参数的作
// 用、取值说明及参数间关系。
Output: // 对输出参数的说明。
Return: // 函数返回值的说明
Others: // 其它说明
**************************************************************************/

参考案例:

1
2
3
4
5
6
7
8
9
10
/************************************************************************
Function: InitChannel
Description: 启动初始化所有通道
Calls: TIM_DeInit
TIM3_Int_Init
Called By: main()
Input: 无
Output: 让所有通道输出低电平
Return: 无
************************************************************************/

程序块注释:

一般的程序块注释如 for,while 之类的注释在前面,程序排版整齐,并方便注释的阅读与理解,将注释与其上面的代码用空一行隔开,且该程序块与下面的代码也应该用空一行(使用/\…*/整体注释,不得采用//行注释)*

1
2
3
4
5
6
7
8
9
10
11
12
例:
void example_fun( void )
{
/* code one comments */
CodeBlock One
/* code two comments */
CodeBlock Two
/* code three comments */
CodeBlock Three
}

边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。命名一般要采用自注释的命名规则(详见命名规范),对于单行代码注释如:函数调用、变量说明等,则直接在行后使用 // 注释。

参考链接:
http://itec.hust.edu.cn/~xujing/cpp/common-cpp/text-book/C-coding-rules-simple.pdf
https://github.com/ruanyf/document-style-guide