C++ : 代码编写规范(二)

目录

5    变量、结构

5-1 防止同名

5-2 去掉没必要的公共变量

5-3 结构功能单一

5-4 数据结构避免过于灵活、全面

5-5 检查结构元素布局与排列

6    函数、过程

6-1 可重入函数使用局部变量

6-2 可重入函数使用全部变量需要保护

6-3 防止将函数的参数作为工作变量

6-4 函数的规模尽量限制在200行以内

6-5 为简单功能编写函数

6-6 避免设计多参数函数

6-7 注意非调度函数

6-8 动宾组词命名函数

6-9 防止把没有关联的语句放到一个函数中

6-10 设计高扇入、合理扇出(小于7)的函数

6-11 避免使用 BOOL 参数

7    可测性

7-1 使用断言,提高可测性

7-2 断言检查非法情况

7-3 断言避免错误情况

7-4 断言确认函数参数

7-5 断言检查开发环境

7-6 正式版本去掉断言

7-7 断言宣布错误发生

8    程序效率

8-1 局部效率应为全局效率服务

8-2 对数据结构的划分与组织的改进,以及算法优化来提高空间效率

8-3 循环体内工作量最小化

8-4 多重循环中,应将最忙的循环放在最内层

8-5 尽量用乘法或其它方法代替除法,特别是浮点运算中的除法

9    宏

9-1 用宏定义表达式时,要使用完备的括号

9-2 使用宏时,不允许参数发生变化


5    变量、结构

5-1 防止同名

        防止局部变量与公共变量同名。

5-2 去掉没必要的公共变量

说明:公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间的耦合度

5-3 结构功能单一

        结构的功能要单一,是针对一种事务的抽象。

示例:如下结构不太清晰、合理。
typedef struct STUDENT_STRU 
{
    unsigned char name[8]; /* student's name */ 
     unsigned char age; /* student's age */ 
     unsigned char sex; /* student's sex, as follows */ 
                     /* 0 - FEMALE; 1 - MALE */ 
     unsigned char 
         teacher_name[8]; /* the student teacher's name */ 
     unsigned char 
         teacher_sex; /* his teacher sex */ 
} STUDENT; 

若改为如下,可能更合理些:

typedef struct TEACHER_STRU 
{ 
 unsigned char name[8]; /* teacher name */ 
 unisgned char sex; /* teacher sex, as follows */ 
 /* 0 - FEMALE; 1 - MALE */ 
} TEACHER; 

typedef struct STUDENT_STRU 
{ 
    unsigned char name[8]; /* student's name */ 
     unsigned char age; /* student's age */ 
     unsigned char sex; /* student's sex, as follows */ 
                     /* 0 - FEMALE; 1 - MALE */ 
     unsigned int teacher_ind; /* his teacher index */ 
} STUDENT;

5-4 数据结构避免过于灵活、全面

        不要设计面面俱到、非常灵活的数据结构。

说明:面面俱到、灵活的数据结构反而容易引起误解和操作困难

5-5 检查结构元素布局与排列

        仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象。

示例:如下结构中的位域排列,将占较大空间,可读性也稍差。
typedef struct EXAMPLE_STRU 
{ 
     unsigned int valid: 1; 
     PERSON person; 
     unsigned int set_flg: 1; 
} EXAMPLE; 

若改成如下形式,不仅可节省 1 字节空间,可读性也变好了。
typedef struct EXAMPLE_STRU 
{
    unsigned int valid: 1; 
    unsigned int set_flg: 1; 
    PERSON person ; 
} EXAMPLE;

6    函数、过程

6-1 可重入函数使用局部变量

        编写可重入函数应注意局部变量的使用(如使用auto即缺省态局部变量或寄存器变量)。

说明:编写 C/C++语言的可重入函数时,不应使用 static 局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

6-2 可重入函数使用全部变量需要保护

        编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段加以保护。

说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态

unsigned int example( int para ) 
{ 
    unsigned int temp; 
    Exam = para; // (**)
    temp = Square_Exam( ); 
    return temp; 
}
// 此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另
// 外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使
// Exam 赋与另一个不同的 para 值,所以当控制重新回到“temp = Square_Exam( )”
// 后,计算出的 temp 很可能不是预想中的结果。此函数应如下改进。

unsigned int example( int para ) 
{ 
    unsigned int temp; 

    [申请信号量操作]
    Exam = para;
    temp = Square_Exam( ); 
    [释放信号量操作]    

    return temp; 
}

6-3 防止将函数的参数作为工作变量

说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改

变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。

示例:下函数的实现不太好。
void sum_data( unsigned int num, int *data, int *sum ) 
{
    unsigned int count; 

    *sum = 0; 
    for (count = 0; count < num; count++) 
    { 
        *sum += data[count]; // sum 成了工作变量,不太好。
    } 
} 

若改为如下,则更好些。
void sum_data( unsigned int num, int *data, int *sum )
{ 
    unsigned int count ; 
    int sum_temp; 

    sum_temp = 0; 
    for (count = 0; count < num; count ++) 
    { 
        sum_temp += data[count]; 
    } 

    *sum = sum_temp; 
}

6-4 函数的规模尽量限制在200行以内

说明:不包括注释和空格行

6-5 为简单功能编写函数

说明:虽然为仅用一两行就可完成的功能去编函数好象没有必要,但用函数可使功能明确

化,增加程序可读性,亦可方便维护、测试。

示例:如下语句的功能不很明显。
value = ( a > b ) ? a : b ; 

改为如下就很清晰了。
int max (int a, int b) 
{ 
    return ((a > b) ? a : b); 
} 
value = max (a, b); 

或改为如下
#define MAX (a, b) (((a) > (b)) ? (a) : (b)) 
value = MAX (a, b);

6-6 避免设计多参数函数

不使用的参数从接口中去掉,减少函数间接口的复杂度

6-7 注意非调度函数

        应减少或防止控制参数,尽量只使用数据参数

目的是防止函数间的控制耦合。调度函数是指根据输入的消息类型或控制命令,来启动相

应的功能实体(即函数或过程),而本身并不完成具体功能。控制参数是指改变函数功能

行为的参数,即函数要根据此参数来决定具体怎样工作。非调度函数的控制参数增加了函

数间的控制耦合,很可能使函数间的耦合度增大,并使函数的功能不唯一。

示例:如下函数构造不太合理。
int add_sub( int a, int b, unsigned char add_sub_flg ) 
{ 
    if (add_sub_flg == INTEGER_ADD) 
    { 
        return (a + b); 
    } 
    else 
    { 
        return (a - b); 
    } 
} 

不如分为如下两个函数清晰。
int add( int a, int b ) 
{ 
    return (a + b); 
}


int sub( int a, int b ) 
{ 
    return (a - b); 
}

6-8 动宾组词命名函数

        使用动宾词组为执行某操作的函数命名。如果是OOP方法,可只有动词(名词是对象本身)

示例:参照如下方式命名函数。

void print_record( unsigned int rec_ind ) ;

int input_record( void ) ;

unsigned char get_current_color( void ) ;

6-9 防止把没有关联的语句放到一个函数中

防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到

同一个函数或过程中

示例:如下函数就是一种随机内聚。
void Init_Var( void ) 
{ 
    Rect.length = 0; 
    Rect.width = 0; /* 初始化矩形的长与宽 */ 

    Point.x = 10; 
    Point.y = 10; /* 初始化“点”的坐标 */ 
} 

//矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。
应如下分为两个函数:
void Init_Rect( void ) 
{ 
    Rect.length = 0; 
    Rect.width = 0; /* 初始化矩形的长与宽 */ 
} 
void Init_Point( void ) 
{ 
    Point.x = 10; 
    Point.y = 10; /* 初始化“点”的坐标 */
}

6-10 设计高扇入、合理扇出(小于7)的函数

扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。

扇出过大:

        表明函数过分复杂,需要控制和协调过多的下级函数;

        一般是由于缺乏中间层次,可适当增加中间层次的函数。

扇出过小:

        如总是1,表明函数的调用层次可能过多,这样不利于程序的阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力;

        可把下级函数进一步分解为多个函数,或合并到上级函数中;

扇入越大:

        表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。

较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。

6-11 避免使用 BOOL 参数

7    可测性

7-1 使用断言,提高可测性

说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),

它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐

藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的

可测性

示例:下面是 C 语言中的一个断言,用宏来设计的。(其中 NULL 为 0L)

#ifdef EXAM_ASSERT_TEST_ // 若使用断言测试
void exam_assert( char * file_name, unsigned int line_no ) 
{ 
    printf( "\n[EXAM]Assert failed: %s, line %u\n", 
        file_name, line_no ); 
    abort( );
} 
#define EXAM_ASSERT( condition ) 
   if (condition) // 若条件成立,则无动作
     NULL; 
   else // 否则报告
     exam_assert( __FILE__, __LINE__ ) 
#else // 若不使用断言测试
#define EXAM_ASSERT(condition) NULL 
#endif /* end of ASSERT */

7-2 断言检查非法情况

        检查程序正常运行时不应发生但在调测时有可能发生的非法情况

7-3 断言避免错误情况

        不能用断言来检查最终产品肯定会出现且必须处理的错误情况

断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错

程序,而不是断言

7-4 断言确认函数参数

示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。
int exam_fun( unsigned char *str ) 
{     
    EXAM_ASSERT( str != NULL ); // 用断言检查“假设指针不为空”这个条件

    ... //other program code 
}

7-5 断言检查开发环境

        用断言对程序开发环境(OS/Hardware)的假设进行检查

对编译器提供的功能及特性假设可用断言检查,原因是软件最终产品(即运行代码或机器

码)与编译器已没有任何直接关系,即软件运行过程中(注意不是编译过程中)不会也不

应该对编译器的功能提出任何需求

示例:用断言检查编译器的 int 型数据占用的内存空间是否为 2,如下。

EXAM_ASSERT( sizeof( int ) == 2 );

7-6 正式版本去掉断言

        正式软件产品中把断言及其它调测代码去掉

加快软件运行速度

7-7 断言宣布错误发生

        编写防错程序,然后在处理错误之后可用断言宣布发生错误

假如某模块收到通信链路上的消息,则应对消息的合法性进行检查,若消息类别不是通信协议中规定的,则应进行出错处理,之后可用断言报告

int msg_handle( unsigned char msg_name, unsigned char * msg ) 
{ 
    switch( msg_name ) 
    { 
    case MSG_ONE: 
        ... // 消息 MSG_ONE 处理
        return MSG_HANDLE_SUCCESS; 

    ... // 其它合法消息处理

    default: 
        ... // 消息出错处理
        ASSERT_REPORT( FALSE ); // “合法”消息不成立,报告
        return MSG_HANDLE_ERROR; 
    } 
}

8    程序效率

8-1 局部效率应为全局效率服务

8-2 对数据结构的划分与组织的改进,以及算法优化来提高空间效率

示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char BYTE; 
typedef unsigned short WORD; 
typedef struct STUDENT_SCORE_STRU 
{
    BYTE name[8]; 
    BYTE age; 
    BYTE sex; 
    BYTE class; 
    BYTE subject; 
    float score; 
} STUDENT_SCORE;


因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进(分为两个结
构),总的存贮空间将变小,操作也变得更方便。

typedef struct STUDENT_STRU 
{
    BYTE name[8]; 
    BYTE age; 
    BYTE sex; 
    BYTE class; 
} STUDENT; 

typedef struct STUDENT_SCORE_STRU 
{ 
    WORD student_index; 
    BYTE subject; 
    float score; 
} STUDENT_SCORE;

8-3 循环体内工作量最小化

说明:应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从

而提高程序的时间效率

示例:如下代码效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) 
{ 
    sum += ind; 
    back_sum = sum; /* backup sum */
} 

语句“back_sum = sum;”完全可以放在 for 语句之后,如下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) 
{ 
    sum += ind; 
} 
back_sum = sum; /* backup sum */

8-4 多重循环中,应将最忙的循环放在最内层

说明:减少 CPU 切入循环层的次数

示例:如下代码效率不高。
for (row = 0; row < 100; row++) 
{ 
    for (col = 0; col < 5; col++) 
    { 
        sum += a[row][col]; 
    } 
}

可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++) 
{ 
    for (row = 0; row < 100; row++) 
    { 
        sum += a[row][col]; 
    } 
}

8-5 尽量用乘法或其它方法代替除法,特别是浮点运算中的除法

说明:浮点运算除法要占用较多 CPU 资源

示例:如下表达式运算可能要占较多 CPU 资源。
#define PAI 3.1416 
radius = circle_length / (2 * PAI);

应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;

9    宏

9-1 用宏定义表达式时,要使用完备的括号

示例:如下定义的宏都存在一定的风险。
#define RECTANGLE_AREA( a, b ) a * b 
#define RECTANGLE_AREA( a, b ) (a * b) 
#define RECTANGLE_AREA( a, b ) (a) * (b) 

正确的定义应为:
#define RECTANGLE_AREA( a, b ) ((a) * (b))

9-2 使用宏时,不允许参数发生变化

示例:如下用法可能导致错误。
#define SQUARE( a ) ((a) * (a)) 
int a = 5; 
int b; 
b = SQUARE( a++ ); // 结果:a = 7,即执行了两次增 1。

正确的用法是:
b = SQUARE( a ); 
a++; // 结果:a = 6,即只执行了一次增 1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值