C/C++

C语言编码规范

2015-12-23  本文已影响0人  rick_2016

1 文件与目录

1.1 文件命名

例如:

  • 模块名称简写+文件名称,如mpMain.c、mpDisp.h
  • 直接使用文件名称,如main.c、disp.h

1.2 目录

  • 文件头注释 <适用于头文件和源文件>
  • 防止重复应用头文件的设置 <适用于头文件>
  • 头文件包含部分 <适用于头文件和源文件>
  • 宏定义部分 <适用于头文件和源文件>
  • 枚举常量声明部分 <适用于头文件和源文件>
  • 类型声明和定义部分,如structuniontypedef等 <适用于头文件和源文件>
  • 全局变量声明部分 <适用于头文件和源文件>
  • 文件级变量声明 <适用于源文件>
  • 全局函数声明 <适用于头文件>
  • 文件级函数声明 <适用于源文件>
  • 函数实现,按函数声明的顺序 <适用于源文件>
  • 文件尾注释 <适用于头文件和源文件>
#include "/project/inc/hello.h"            /* NG: 不应使用绝对路径 */
#include "../inc/hello.h"                       /* OK: 可以使用相对路径 */

示例:

#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;       /* OK: 在模块1的.h文件中声明变量 */
uint8_t g_ucPara;   /* NG: 在模块1的.h文件中定义全局变量g_ucPara*/

2 排版

示例:

void demoFunc( void )
{
    uint8_t i;
                               /* 此处为空行, 局部变量和语句间空一行 */
    /* 功能块1 */
    for ( i = 0; i < 10; i++ )
    {
        //...
    }
                               /* 此处为空行,不同的功能块间空一行*/
    /*功能块2*/
    for ( i = 0; i < 20; i++ )
    {
        //...
    }
}

示例:

if( ( ucParam1 == 0 ) && ( ucParam2 == 0 ) && ( ucParam3 == 0 ) 
      || ( ucParam4 == 0 ) )<----长表达式需要换行书写
{
    //...
}

示例:

rect.length = 0; rect.width = 0; // NG:  一行多条语句
rect.length = 0; // OK: 一行一条语句
rect.width = 0;

示例:

void func( void )
{
    for(...){              /* NG: {需要单独占用一行 */
        //.../*programcode*/
    }

    for(...)
    {                      /* 规范的写法 */
        //.../*programcode*/
    }
}
int_32 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 注释

/****************************************************************************
*Copyright(C),2010-2011,武汉汉升汽车传感系统有限责任公司
*文件名: main.c
*内容简述:*
*文件历史:
*版本号日期作者说明
*01a2010-07-29王江河创建该文件
*01b2010-08-20王江河改为可以在字符串中发送回车符
*02a2010-12-03王江河增加文件头注释
*/

示例:
下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

/****************************************************************************
*函数名:SendToCard()
*功能:向读卡器发命令,如果读卡器进入休眠,则首先唤醒它
*输入:全局变量gaTxCard[]存放待发的数据
*全局变量gbTxCardLen存放长度
*输出:无
*/

说明:错误的注释不但无益反而有害。注释主要阐述代码做了什么(What),或者如果有必要的话,阐述为什么要这么做(Why),注释并不是用来阐述它究竟是如何实现算法(How)的。

说明:在使用缩写时或之前,应对缩写进行必要的说明。

例1:不规范的写法

void func( void )
{
    /*获取复本子系统索引和网络指示器*/
                                                          //NG: 不规范的写法,此处不应该空行
    repssnInd = ssnData[index].repssnIndex;
    repssnNi = ssnData[index].ni;
}

例2:不规范的写法

void func( void )
{
    repssnInd = ssnData[index].repssnIndex;
    repssnNi = ssnData[index].ni;
    /* 获取复本子系统索引和网络指示器 */ // NG: 不规范的写法,应该在语句前注释
}

例3:规范的写法

void func( void )
{
    /*获取复本子系统索引和网络指示器*/
    repssnInd = ssnData[index].repssnIndex;
    repssnNi = ssnData[index].ni;
}

例4:不规范的写法,显得代码过于紧凑

void func( void )
{
    /*获取复本子系统索引*/
    repssnInd = ssnData[index].repssnIndex;
    /* *获取复本子系统网络指示器 */ // NG: 代码与上一行注释之间需要空行
    repssnNi = ssnData[index].ni;
}

例5:规范的写法

void func( void )
{
    /*获取复本子系统索引*/
    repssnInd = ssnData[index].repssnIndex;

    /* *获取复本子系统网络指示器 */
    repssnNi = ssnData[index].ni;
}

说明:这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。

说明:这样比较清楚程序编写者的意图,有效防止无故遗漏break语句。示例(注意斜体加粗部分):

switch ( cmd )
{
    case CMD_DOWN:
        ProcessDown();
        break;
    case CMD_FWD:
        ProcessFwd();
        if (...)
        {
            ...
            break;
        }
        else
        {
            ProcessCFW_B();        /*now jump into case CMD_A*/
        }
    case CMD_A:
        ProcessA();
        break;
    default:
        break;
}

说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

4 标识符命名

说明:较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。
示例:如下单词的缩写能够被大家基本认可。

temp可缩写为 tmp;
flag可缩写为 flg;
statistic可缩写为 stat;
increment可缩写为 inc;
message可缩写为 msg;

5 可读性

表达式正确的写法示例:

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 )  /* 造成了判断条件出错 */

示例:如下的程序可读性差。

if ( trunk[index].trunkState == 0 )<----不规范的写法,应使用有意义的标识
{
    trunk[index].trunkState=1;<----不规范的写法,应使用有意义的标识.../*programcode*/
}

应改为如下形式。

enum trunkState_e
{
    TRUNK_IDLE=0,
    TRUNK_BUSY=1
};
if ( trunk[index].trunkState == TRUNK_IDLE )
{
     trunk[index].trunkState=TRUNK_BUSY;
    //.../*programcode*/
}

说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。示例:如下表达式,考虑不周就可能出问题,也较难理解。

*stat_poi+++ = 1;
*++stat_poi += 1;

应分别改为如下。

*stat_poi += 1;
stat_poi++;      /* 此二语句功能相当于 *stat_poi+++ = 1;  */
++stat_poi;
*stat_poi += 1;    /* 此二语句功能相当于*++stat_poi += 1;  */

6 变量、结构、常量、宏

变量按作用空间可以分为全局变量与局部变量(临时变量)静态变量,其空前前缀为g_,t_,s_;
变量按类型可以分为逻辑型、字符型、整型、长整型、字符串、浮点数、结构类型、指针,其类型前缀为b,c,I,g,s,f,str,p。
规范要求
变量的命名采用空间前缀+类型前缀+驼峰法;举例

uchar g_cMeterAddr;      //全局变量,单字节,表地址
uchar g_iTime;           //全局变量,双字节,时间
uchar t_cLength;         //局部变量,单字节,长度
uchar g_bKeyInputOver;   //全局变量,逻辑变量,键盘输入结束
uchar g_sCommBuff[5];    //全局变量,字符串,通讯缓冲
uchar t_fValue;          //局部变量,浮点数,数据
uchar g_strDCBTbl;       //全局变量,结构类型,表
uchar* g_pCommBuff;      //全局变量,指针,通讯缓冲
typedef unsigned char       uint8_t;
typedef unsigned short      uint16_t;
typedef unsigned long int   uint32_t;
typedef signed char         int8_t;
typedef signed short        int16_t;
typedef signed long int     int32_t;
#define __IO                volatile
变量类型 前缀 举例
uint8_t uc ucSum
int8_t c cSum
uint16_t us usParaWord
int16_t s sParaWord
uint32_t ul ulParaWord
int32_t l lParaWord
uint8_t * ucp ucpWrite
int8_t * cp cpWrite
uint16_t* usp uspWrite
int16_t* sp spWrite
uint32_t * ulp ulpWrite
int32_t * lp lpWrite
uint8_t[] uca ucaNum[5]
int8_t[] ca caNum[5]
uint16_t[] usa usaNum[5]
int16_t[] sa saNum[5]
uint32_t[] ula ulaNum[5]
int32_t[] la laNum[5]
结构体 t tPara

为了清晰的标识变量的作用域,减少发生命名冲突,应该在变量类型前缀之前再加上表示变量作用域的前缀,并在变量类型前缀和变量作用域前缀之间用下划线‘-’隔开。具体的规则如下:

uint32_t g_ulParaWord;
uint8_t g_ucByte;
static uint32_t s_ulParaWord;
static uint8_t s_ucByte;
/*结构体命名类型名*/
typedef struct tagBillQuery_t
{
    //...
}billQuery_t;

/*结构体变量定义*/
billQuery_t tBillQuery;
typedef enum
{
    KB_F1=0,          /* F1键代码 */
    KB_F2,            /* F2键代码 */
    KB_F3             /* F3键代码 */
}keyCODE_e;
#define LOG_BUF_SIZE 8000

6 函数

如:uartReceive(串口接收)
备注:对于非常简单的程序,可以不加模块名。

/******************************************************************************
 * 函数名:uartConvUartBaud
 *功能:波特率转换
 * 输入:_ulBaud:波特率
 *输出:无
 * 返回:uint32-转换后的波特率值
 */
uint32_t uartConvUartBaud( uint32_t a_ulBaud )
{
    uint32_t ulBaud;
    ulBaud = a_ulBaud * 2;  /*计算波特率*/
    //......
    return ulBaud;
}

说明:避免用含义不清的动词如process、handle等为函数命名,因为这些动词并没有说明要具体做什么。
示例:参照如下方式命名函数。

void printRecord( uint32_t  recInd );
int32_t inputRecord( void );
uint8_t getCurrentColor( void );

说明:如果约定由调用方检查参数输入,则应使用assert()之类的宏,来验证所有参数输入的有效性。

-6 检查函数所有非参数输入的有效性,如数据文件、公共变量等。

说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入之前,应进行必要的检查。

说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。

说明:目的减少函数间接口的复杂度,复杂的参数可以使用结构传递。

说明:因为数据类型转换或多或少存在危险。

说明:原因有二,其一是BOOL参数值无意义,TURE/FALSE的含义是非常模糊的,在调用时很难知道该参数到底传达的是什么意思;其二是BOOL参数值不利于扩充。还有NULL也是一个无意义的单词。

说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到同一个函数或过程中。随机内聚给函数或过程的维护、测试及以后的升级等造成了不便,同时也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。在编程时,经常遇到在不同函数中使用相同的代码,许多开发人员都愿把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产生随机内聚的函数。示例:如下函数就是一种随机内聚。

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;      /*初始化“点”的坐标*/
}

说明:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。

说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。

上一篇 下一篇

猜你喜欢

热点阅读