C 语言编程规范和范例
本文定义了麦格斯 C 语言编程规范,统一编程风格。由于编程规范涉及条目很杂很多,本文只对较易出错和重要的几条做了硬性要求。
1.文件结构
C 语言程序一般都由两个文件组成,一个是头文件(文件名以.h 后缀命名),用于保存程序的声明(declaration);另一个是定义文件(文件名以.c 后缀命名),用于保存程序的实现(implementation)。
【规则 1-1】版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头(参见示例 1-1),主要内容有:
- 版权信息。
- 文件名称,标识符,摘要。
- 当前版本号,作者/修改者,完成日期。
- 版本历史信息。
/********************************************************************* * Copyright (c) 2010,深圳市麦格斯科技有限公司软件部 * All rights reserved. * * 文件名称:filename.h 或 filename.c * 摘要:简要描述本文件的内容 * * Current Version 1.2--作者或修改者名字(如 ck)--完成日期(如 2010.7.20) * 描述:简要描述版本修订内容 * * Previous Version 1.1--作者或修改者名字(如 ck)--完成日期(如 2010.1.1) * 描述:简要描述版本修订内容 * * Previous Version 1.0--作者或修改者名字(如 ck)--完成日期(如 2009.11.6) * 描述:简要描述版本修订内容
示例 1-1 版权和版本的声明
【规则 1-2】头文件的结构规则
头文件由三部分内容组成:
- 头文件开头处的版权和版本声明(参见示例 1-1);
- 预处理块;
- 函数和类结构声明等。
假设头文件名称为 graphics.h,头文件的结构参见示例 1-2。
// 版权和版本声明见示例 1-1,此处省略。 #ifndef GRAPHICS_H // 防止 graphics.h 被重复引用 #define GRAPHICS_H #include <math.h> // 引用标准库的头文件 … #include “myheader.h” // 引用非标准库的头文件 … void Function1(…); // 全局函数声明 … class Box // 类结构声明 { … }; #endif /*GRAPHICS_H*/
示例 1-2 C 头文件的结构
【规则 1-2-1】防止重复引用规则
为了防止头文件被重复引用,应当用 #ifndef/#define/#endif
结构产生预处理块。用法详见示例 1-2 所示。
【规则 1-2-2】头文件引用规则
- 用
#include <filename.h>
格式来引用标准库的头文件(编译器将从标准库目录开始搜索); - 用
#include “filename.h”
格式来来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
用法详见示例 1-2 所示。
【规则 1-2-3】建议规则
- 头文件中应尽量做到只存放“声明”而不存放“定义”;
- 头文件中不提倡使用全局变量,尽量不要在头文件中出现象 extern int value 这类声明;
- 头文件中尽可能不要出现定义全局变量,否则会很容易造成多重定义现象。
【规则 1-3】定义文件的结构规则
定义文件由三部分内容组成:
- 定义文件开头处的版权和版本声明(参见示例 1-1);
- 对一些头文件的引用;
- 程序的实现体(包括数据和代码)。
假设定义文件的名称为 graphics.c,定义文件的结构参见示例 1-3。
#ifdef __cplusplus extern "C"{ #endif // 版权和版本声明见示例 1-1,此处省略。 #include “graphics.h” // 引用头文件 … // 全局函数的实现体 void Function1(…) { … } #ifdef __cplusplus } #endif
示例 1-3 C 定义文件的结构 为支持 C++能够调用 C 函数接口,在 C 定义文件中应该使用#ifdef __cplusplus/extern "C" {/#endif 结构,详细用法见示例 1-3 的开始和结尾#预编译语句所示。
2.命名规则
【规则 2-1】函数名命名规则
函数名用大写字母开头的单词组合而成,有多个单词混合组成时每个单词的首字母大写,函数名应该准确描述函数的功能,在定义和声明函数时不能省略掉形参,参数为空的函数也不应该省略掉 void。 例如:
int GetSysState(void);
int SetSysSatae(int iState);
【规则 2-2】变量命名规则
函数内部的局部临时变量可以直接用 i,j,k 等单字母命名即可,但是函数参数、全局变量以及具有重要意义的局部变量等必须采用简化版的匈牙利命名法。
前缀 | 类型 | 描述 | 实例 |
ch | char | 8 位有符号字符 | chGrade |
ach | array of char | 8 位有符号字符数组 | achString[100] |
uch | unsigned char | 8 位无符号字符 | uchName |
b | BOOL | 布尔值 | bEnable |
i | int | 有符号整型值(其大小依赖于操作系统) | iLength |
ui | unsigned int | 无符号整型值(其大小依赖于操作系统) | uiHeight |
s | short | 16 位短型有符号值 | sPos |
us | unsigned short | 16 位短型无符号值 | usPos |
l | long | 32 位有符号整型 | lOffset |
ul | unsigned long | 32 位无符号整型 | ulOffset |
p | * | 指针 | pDoc |
pp | ** | 指向指针的指针 | ppPtr |
st | struct | 结构体 | stXthread |
un | union | 联合体(共用体) | unName |
示例 2-1 常用变量类型命名
【规则 2-2-1】 常用基本类型变量命名法则
简化版匈牙利变量命名规则总体归纳为:数据类型前缀+逻辑单词名,逻辑单词名第一个字母为大写字母,逻辑单词名尽量要表达出该变量的用途含义,这样可以根据每个变量名本身快速确定该变量的类型及用途含义,方便阅读。 示例 2-1 列出了编程中涉及到的常用变量类型命名,详细列出了常用数据类型的前缀及实例命名法则。 特别需要指出的是,指针变量的命名比一般的基本类型变量特殊,命名时除了用前缀 p 指明其为指针外,还须指明其所指的数据类型。 例如:
char *pchFile = NULL;
pchFile 可以很明显看出是一个指向字符型数据类型的指针,p 为指针,ch 为字符型。
int *piRet = NULL;
piRet 可以很明显看出是一个指向整型数据类型的指针,p 为指针,i 为整型。
【规则 2-2-2】 数组命名法则
数组命名时,只需要按【规则 2-2-1】的变量命名规则命名后,再在前面加一个 a 前缀即可,如示例 2-1 中第 3 行所示,achString[100],表示定义了一个有符号的字符型数组,长度是 100。
【规则 2-2-3】 结构体命名法则
- 结构体类型命名必须全部用大写字母,用下划线分割单词,并以_T 结尾:
例如:
typedef struct SDP_MEDIA_s { int iType; int iPort; //端口 int iFmt; //媒体格式 char achFmt[MAX_STRING_LEN]; //流压缩格式 char achAddr[MAX_STRING_LEN]; //地址 }SDP_MEDIA_T;
- 结构体变量命名必须以 st 为前缀命名,st 后接变量名,首字母必须为大写字母:
例如: SDP_MEDIA_T stSdpMedia;
【规则 2-2-4】 联合体命名法则
(1)联合体类型命名必须全部用大写字母,并以_T 结尾: 例如:
typedef union ROOMADDR_s { char achRoomAddr[ROOMADDRESS_LEN]; ROOMNUMBER_T stRoomNumber; }ROOMADDR_T;
(2)结构体变量命名必须以 st 为前缀命名,st 后接变量名,首字母必须为大写字母: 例如: SDP_MEDIA_T stSdpMedia;
【规则 2-2-5】 枚举类型命名法则
(1)枚举类型命名必须全部用大写字母,用下划线分割单词,并以_EN 结尾: 例如:
typedef enum MEDIA_TYPE_en { MEDIA_NONE = 0x0, MEDIA_AUDIO = 0x1, MEDIA_VIDEO = 0x2, MEDIA_AVALL = 0x3 }MEDIA_TYPE_EN;
(2)枚举变量命名必须以 en 为前缀命名,en 后接变量名,首字母必须为大写字母: 例如: MEDIA_TYPE_EN enMediaType;
【规则 2-2-6】 自定义数据类型命名法则
(1)自定义数据类型命名必须全部用大写字母,用下划线分割单词,并以 UD 结尾: 例如:
typedef unsigned char BYTE_UD;
typedef unsigned short WORD_UD;
typedef unsigned int DWORD_UD;
typedef int (*PTR_PROC_UD)(void *);
(2)自定义数据类型的变量命名必须以 ud 为前缀命名(ud 表示 user-defined),ud 后接变量名,首字母必须为大写字母: 例如: PTR_PROC_UD udPtrproc;
【规则 2-2-7】 静态变量命名法则
静态变量加前缀 s_(表示 static),前缀后的命名应该遵守【规则 2-2-1】的简化版匈牙利命名法则。如定义一个 int 型的静态变量:static int s_iFlag;
【规则 2-2-8】 全局变量命名法则
尽量少使用全局变量,定义全局变量时应加前缀 g_(表示 global),前缀后的命名应该遵守【规则 2-2-1】的简化版匈牙利命名法则。。 例如: int g_iState;
全局变量的使用经常使得函数接口变得不可重入,尤其一个全局变量在多个 c 文件中调用使得程序结构变得非常不严谨,也增加了出错的可能性。在不得已使用全局变量时,也应该尽可能使用静态全局变量,只在一个 c 文件中可见。定义静态全局变量时,应加前缀 sg_(表示 static global),前缀后的命名应该遵守【规则 2-2-1】的简化版匈牙利命名法则。 例如: static int sg_iFlag;
【规则 2-2-9】 常量命名法则
常量全用大写的字母,用下划线分割单词。 例如:
const int MAX = 100;
const int MAX_LENGTH = 100;
【规则 2-2-10】 宏定义命名法则
使用宏定义表达式时,宏名必须要全部用大写字母撰写,宏定义表达式要使用完备的括号。将宏定义的多条表达式放在大括号中,避免 if、for 等语句书写不规范造成代码漏执行。 例如: #define RECTANGLE_AREA( a, b ) ((a) * (b))
3.程序的排版风格
程序排版虽然不会影响程序的功能,但会影响可读性。定义一套风格良好一致的版式风格,能让人对程序一目了然,增加阅读兴趣。
【规则 3-1】 空行
空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得。 【规则 3-1-1】在每个类声明之后、每个函数定义结束之后都要加空行。参见示例 3-1(a) 所示。 【规则 3-1-2】在一个函数体内,逻辑上密切相关的语句之间不加空行,其它地方应加空行分隔。参加示例 3-1(b) 所示。
// 空行 void Function1(…) { … } // 空行 void Function2(…) { … } // 空行 void Function3(…) { … } | // 空行 while (condition) { statement1; // 空行 if (condition) { statement2; } else { statement3; } // 空行 statement4; } |
示例 3-1(a) 函数之间的空行 示例 3-1(b) 函数内部的空行
【规则 3-2】 代码行
【规则 3-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。 【规则 3-2-2】if、for、while、do 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{},即使只有一行也需要加{}。这样可以防止书写失误。 【规则 3-2-3】尽可能在定义变量的同时初始化该变量。 【规则 3-2-4】if、for、while、switch 等与后面的括号间应加空格,使 if 等关键字更为突出、明显。 示例 3-2(a)为风格良好的代码行,示例 3-2(b)为风格不良的代码行。
int width = 0; // 宽度 | int width, height, depth; // 宽度高度深度 |
x = a + b; | x = a + b; y = c + d; z = e + f; |
if (width < height){ | if (width < height) dosomething(); |
for (initialization; condition; update){ | for (initialization; condition; update) |
示例 3-2(a) 风格良好的代码行 示例 3-2(b) 风格不良的代码行
【规则 3-3】 代码行内的空格
【规则 3-3-1】关键字之后要留空格。象 const、virtual、inline、case 等关键字之后至少要留一个空格,否则无法辨析关键字。象 if、for、while 等关键字之后应留一个空格再跟左括号‘(’,以突出关键字。 【规则 3-3-2】函数名之后不要留空格,紧跟左括号‘(’,以与关键字区别。 【规则 3-3-3】符号‘(’向后紧跟;符号‘)’、‘,’、‘;’向前紧跟,紧跟处不留空格。 【规则 3-3-4】‘,’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束符号,其后要留空格,如 for (initialization; condition; update)。 【规则 3-3-5】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。 【规则 3-3-6】一元操作符如“!”、“~”、“++”、“--”、“&”(地址运算符)等前后不加空格。 【规则 3-3-7】象“[ ]”、“.”、“->”这类操作符前后不加空格。 【规则 3-3-8】对于表达式比较长的 for 语句和 if 语句,为了紧凑起见可以适当地去掉一些空格,如 for (i=0; i<10; i++) 和 if ((a<=b) && (c<=d))。 示例 3-3 描述了多种良好和不良好风格的代码行内的空格。
void Func1(int x, int y, int z); // 良好的风格 |
if (year >= 2000) // 良好的风格 |
for (i=0; i<10; i++) // 良好的风格 |
x = a < b ? a : b; // 良好的风格 |
int *x = &y; // 良好的风格 |
array[5] = 0; // 不要写成 array [ 5 ] = 0; |
示例 3-3 代码行内的空格
【规则 3-4】 对齐
【规则 3-4-1】程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。 【规则 3-4-2】程序块要采用缩进风格编写,缩进的空格数为 4 个,可以将 Source Insight 或 UltraEdit 工具将键盘 TAB 按键设置为 4 个空格数,按一下 TAB 键即可完成 4 空格缩进。{ }之内的代码块在‘{’右边 4 空格处左对齐。 示例 3-4(a)为风格良好的对齐,示例 3-4(b)为风格不良的对齐。
void Function(int x) | void Function(int x){ |
if (condition) | if (condition){ |
for (initialization; condition; update) | for (initialization; condition; update){ |
While (condition) | while (condition){ |
如果出现嵌套的{},则使用缩进对齐,如:{ … { … } … } |
示例 3-4(a) 风格良好的对齐 示例 3-4(b) 风格不良的对齐
【规则 3-5】 长行拆分
【规则 3-5-1】代码行最大长度宜控制在 70 至 80 个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。 【规则 3-5-2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
if ((very_longer_variable1 >= very_longer_variable12) && (very_longer_variable3 <= very_longer_variable14) && (very_longer_variable5 <= very_longer_variable16)) { dosomething(); } |
for (very_longer_initialization; very_longer_condition; very_longer_update) { dosomething(); } |
示例 3-5 长行的拆分
【规则 3-6】 修饰符的位置
修饰符* 和& 应该靠近数据类型还是该靠近变量名,是个有争议的活题。 若将修饰符* 靠近数据类型,例如:int* x; 从语义上讲此写法比较直观,即 x 是 int 类型的指针。 上述写法的弊端是容易引起误解,例如: int* x, y; 此处 y 容易被误解为指针变量。虽然将 x 和 y 分行定义可以避免误解,但并不是人人都愿意这样做。 【规则 3-6-1】应当将修饰符* 和& 紧靠变量名。 例如:
char *pchName;
int *x, y; // 此处 y 不会被误解为指针
【规则 3-7】 注释
注释在程序开发中占有很重要的地位,准确的注释可以有助于阅读者理解代码。注释通常在如下几个地方是必须要存在的:
- 版本、版权声明;
- 函数接口说明;
- 重要的代码行或段落提示;
- 在修改或者维护代码过程中,增加、编辑修改、删除程序语句时,需要打上必要的注释,如日期、修改者姓名等,方便他人查找修改的部分。
【规则 3-7-1】一般情况下,源程序有效注释量必须在 20%以上。但是注释也不能过多,毕竟注释不是文档,并且注释的花样要少。 【规则 3-7-2】边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。 【规则 3-7-3】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。 【规则 3-7-4】当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。 【规则 3-7-5】说明性文件(如头文件.h 文件、.inc 文件、.def 文件、编译说明文件.cfg 等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。 【规则 3-7-6】源文件头部应进行注释,列出:版权说明、版本号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。参考示例 1-1 所示。 【规则 3-7-8】函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数、表)等。参考示例 3-7 所示。
/************************************************************************* * 函数介绍: * 输入参数说明: * 输出参数说明: * 返回值: **************************************************************************/ void Function(float x, float y, float z) { … }
示例 3-7 函数的注释 【规则 3-7-9】数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。特别是全局变量要有较详细的注释。 【规则 3-7-10】在移植或者优化修改第 3 方软件(比如 uboot、内核、第 3 方库等源代码),应该将自己的每一个修改地方打上注释标签,当出错或者阅读时能快速查找修改的部分。注释标签应该必须将修改者姓名(如中文名则必须写成拼音全称,如王碧,应写为 wangbi) 和修改日期加入到注释中,具体格式为 modified by xxxx yy-mm-dd。 例如: /*modified by wangbi 2010-09-09*/
【规则 3-7-11】注释格式尽量统一,建议使用 /* …… */
。
4.安全性检测规则
【规则 4-1】检查函数所有参数输入的有效性,应该正确使用“断言”(assert)来防止此类错误发生。 【规则 4-2】检查函数所有非参数输入的有效性,如数据文件、公共变量等,特别是对于指针的使用,在使用指针前必须判断指针是否有效。 【规则 4-3】任何变量,指针定义时都需要初始化。 【规则 4-5】尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。在 C 语言中,函数的 static 局部变量是函数的“记忆”存储器。在函数内部,除非必要,建议尽量少使用 static 局部变量。 【规则 4-6】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。建议成功返回值为 0,出错返回值小于 0。
5.参考资料
主要参资料:
- 《高质量 C/C++编程指南》
- 《华为编程规范和范例》
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论