C++编码规范要求

目录

1 版式

1.1 类的版式

1.2 空行

1.3 代码行

1.4 代码行内的空格

1.5 注释

2 命名规则

2.1 变量

2.2 常量

2.3 枚举

2.4 函数

2.5

2.6 文件命名

2.7

3 函数设计

3.1 返回值

3.2 参数

3.3 函数体

4 基本语句

4.1 if语句

4.2 switch语句

4.3 for语句

5 内存管理

5.1 内存使用

5.2 new/delete

6 异常处理

6.1 断言

6.2 异常提示

6.3 日志

6.4 组合方式

7 程序效率

7.1 原则

7.2 措施

8 其他

  1. 版式
    1. 类的版式
  1. //【重要】建议将public类型接口在前面,private类型的接口写在后面,类的成员变量尽量声明为privateprotected属性。(1-16) [classInterface]  [mVariableStatement]

示例:

class ClassA

{

public:

void Func1();

void Func2();

protected:

……

private:

int    m_iX;

float m_fY;

}

    1. 空行
  1. //【重要】每个类的声明之后,每个函数定义结束之后都要空行。(1-16)  [blankLine]

示例:

void Test::Function()

{

// code block

}

void Test::Function2()

{

// code block

}

  1. //【优先】代码结构中,在逻辑上密切相关的语句之间不加空行相对独立的程序块之间加空行进行分隔。(if、for、while、switch、do…while)(1-17) [blankLine]

示例: 

void Function()

{

// code block

while (...)

{

...

}

// code block

}

    1. L代码行
  1. //【一般】一行代码只做一件事情否者需要拆分成多行。[1-4](逗号来识别)[new line]

示例: 

int i_data  = 0; // 定义时初始化。

int i_index = 0;

  1. ?【一般】同一逻辑的声明/定义块,建议尽量将初始化代码对齐,增加清晰度。(成员变量定义/声明,宏)TODO  [left_Aligned]

示例: 

DWORD   dw_ret_val    = 0;

DWORD*  p_ret_val     = NULL;

char*   p_ret_val     = NULL;

char    c_ret_val     = 0

  1. //【重要】If, for, while, do语句独占一行,执行语句另起一行,即便执行语句只有一行也必须加分界符“{”和“}”。(1-4) (if、for、while、switch、do…while) (1-17)(增加对条件表达式占多行的判断) [delimiter]  [executeStatement]
  2. //【重要】程序的分界符“{”和“}”应该独占一行且位于同一列,同时与引用他们的语句左对齐。(对齐TODO)(class、函数、if、for、while、switch、do…while、else)(1-17))[delimiter]    

if示例

if (condition)

{

if (bar)

{

Win();

}

else

{

Lose();

}

}

for示例

for (int i = 0; i < count; i++)

{

...

continue;

}

while示例

int i = 0;

while (i < count)

{

...

continue;

}

do…while示例

do

{

...

break;

}while (i < count);

switch…case示例

switch (index)

{

case value1:

{

...

break;

}

case value2:

{

i = value2;

break;

}

default:

{

i = 0;

break;

}

}

  1. //【重要】一行代码的最大长度限制建议设置120,如果超过需要进行分行
  2. 【重要】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首;拆分出的新行要进行适当的缩进,使排版整齐,语句可读。(当行宽度大于等于120,且操作符多三个时,提示需要拆分且操作符位于行首)(1-17)[lineLength] [longExpression]

示例1:

if ((very_longer_variable1 >= very_longer_variable12)

&& (very_longer_variable3 <= very_longer_variable14)

&& (very_longer_variable5 <= very_longer_variable16))

{

dosomething();

}

    1. 代码行内的空格
  1. //【重要】代码中不同的层级关系使用缩进行区分;(1-18) [whitespace/retract]

示例:

namespace SpaceA

{

class ClassA

{

public:

void Func1();

……

private:

int m_iX;

};

}

  1. //【一般】代码缩进统一使用空格,一个Tab长度对应为4个空格
  2. //【重要】关键字之后要留空格。(1-4) 强制为4个空格

const、virtual、inline、case等关键字之后至少要留一个空格,否则无法辨析关键字;//像if、for、while 等关键字之后应留一个空格再跟左括号“(”,以突出关键字。(1-18

示例:见第一章第3节条目(4)示例。

  1. //【重要】函数名之后不要留空格,紧跟左括号“(”

示例:

void Function();

  1. //【一般】左括号(”向后紧跟,右括号)”、逗号,分号“;”向前紧跟,紧跟处不留空格

示例:

void Func1(int iX, int iY, int iZ);

  1. //【重要】逗号,”之后要留空格

示例:

Function(x, y, z);

  1. //【一般】如果分号“;”不作为一行的结束符号,其后要留空格

示例:

for (initialization; condition; update);

  1. //【重要】赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符二元操作符的前后应当加空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等

示例:

if (currentCount > total);

x = a < b ? a : b;

  1. //【重要】一元操作符前后不加空格如“!”、“~”、“++”、“--”、“&”(地址运算符)等。(1-4)

示例:

++a;

b--;

int* x = &y;

  1. //【重要】像[]”、“.”、“->”这类操作符前后不加空格。(1-4)

示例:

i_array[5] = 0;         // 不要写成 i_array [ 5 ] = 0;

object.Function();     // 不要写成 object . Function();

p_object->Function(); // 不要写成 p_object -> Function();

    1. 注释
  1. 【重要】注释是对代码的“提示”,帮助其他编程人员对代码的理解,主要用于
  • 版本、版权声明;
  • 函数接口声明;---------------TODO
  • 重要的代码行或段落提示
  1. X【一般】尽量追求程序代码的逻辑清晰可读,以减少注释的辅助作用
  2. X【一般】注释应该简单易于理解,遵循“KISS”(Keep It Simple and Stupid)原则
  3. X【一般】尽量避免在注释中使用缩写,特别是不常用缩写
  4. //【重要】注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。                                  (1-18

示例

// 一切正确,提交

if (...)

{

float   f_balance;   // Money in account

int     i_min;        // min number for …

int     i_max;        // max number for …

...

}

  1. //【重要】在文件的起始处建议写明文件注释,以简要说明文件/类的功能以及重要的大事件;对于一般的修改历史信息,建议使用版本控制工具记录。(1-4) introduction    [introduction/copyright] [introduction/name] [introduction/description] [introduction/modifiedAndAuthor]

示例: 

// ------------------------------------------------------------

// Copyright (c) 2015, 深圳开立科技有限公司。All rights reserved.

// 名  字: KUltraSoundImageSystem.h

// 描  述: 超声图像系统

// 修  改: 2015-06-18, zjm

// ------------------------------------------------------------

  1. ?【重要】公共接口必须提供注释,以方便用户的理解和使用

示例: 

// ------------------------------------------------------------

// 描述:获取血流体积的结果

// 参数:

//  输入:

// int x1: 第一点的x坐标相对于屏幕左上角的绝对坐标

// int y1: 第一点的y坐标相对于屏幕左上角的绝对坐标

// int x2: 第二点的x坐标相对于屏幕左上角的绝对坐标

// int y2: 第二点的y坐标相对于屏幕左上角的绝对坐标

// 输出:

// doubule* flowResult: flowResult[0]:线上的最大速度,

//   flowResult[1]:线上的平均速度

// 返回值:bool, true--计算成功,false--计算失败

// 异常/错误:无

// ------------------------------------------------------------

  1. 【一般】对于类和接口的注释推荐使用doxygen件标准格式,以方便生成软件文档
  2. //【重要】全局变量、静态变量都要提供对应的注释。(1-29)

示例: 

// 截断行变量。非0,截断行显示;0,连续行

int s_iTruncateLines;

  1. X【一般】尽量注释Why,而不仅仅是What

示例: 

// 根据需求文档XXXX条目的要求,对于缴税额大于1000万的,优惠5%

if (1000.00 <= fGrandTotal)

{

f_grand_total = fGrandTotal * 0.95;

}

  1. X【重要】修改代码的同时要修改相应的注释,以保证注释与代码的一致性
  2. //【重要】不再有用的注释要及时删除。 (1-18)   if 0
  1. 命名规则
    1. 变量
  1. X【一般】变量名用名词或者名词加形容词

示例:

int i_old_value;

float f_page_time;

vector<AngleState>  vct_angle_state;

map<string, int>    map_calc_result;

  1. //【重要】变量前面加上类型的缩写。  (1-22)

标识符

说明

示例

p

pointer

Finger* p_rude;

某些情况下,p 和基本类型一起使

只需要写明p即可。例如,int* p_counter

str

string

string str_name;

sz

psz

‘\0’结束的字符串

char sz_name[10];

char* p_name;

h

handle

HWND h_window;

c

character (char)

char c_letter;

uc

unsigned char

unsigned char uc_revdata[12]

i

int

int i_counter;

f

float

float f_salary;

d

double

double d_commission;

b

bool

bool b_isopen;

u

unsigned

unsigned int ui_number;

w

WORD

WORD w_value;

l

long

long l_number;

ll

 long long

long long ll_num

dw

DWORD or unsigned long

integer

DWORD dw_longnum;

e

enum

E_CALC_STAGE e_stage;

  1. X【重要】用正确的反义词来描述具有相反意义或者互斥的变量

示例:

int i_min_val;

int i_max_val;

int GetValue();

int SetValue(int iVal);

  1. X【一般】变量命名中使用特色命名或者缩写要有注释说明

示例:

int i_mea_state; //测量状态

  1. 【重要】程序中不要出现仅依靠大小写区分的相似的标识符

示例:

int i_param;//错误

int i_Param;//错误

  1. 【重要】程序中不要出现标识完全相同的局部变量和全局变量

示例:

int  g_iLastIndex;

int  i_last_index;

  1. //【一般】变量中避免出现数字编号,除非逻辑上需要(1-22)

示例:

int i_param1;//错误

int i_param2 //错误

  1. //【重要】变量的定义。  (1-23) [VariableName]

变量类型

定义描述

实例

全局变量

全局变量前面加上g_

int g_iRecvData;

静态变量

静态变量前面加上s_

static int s_iRecvDate;

成员变量

成员变量前面加上m_

int m_iRecvData;

局部变量

局部变量全部小写,单词之间用_分隔

int i_last_index

  1. //【重要】函数的参数首单词小写,其他单词首字母大写 (1-22) [fn_parameter]

示例:

DrawText(const Point& point, int iOffset, const char* pRuleNo);

    1. 常量     [const]
  1. //【重要】常量全用大写字母并用下划线分割单词。(1-21)

示例:

const int MAX_DATA =1000;

  1. //【一般】尽量使const替代#define定义常量。(1-21) [#define]
  2. //【重要】魔法数字用常量定义。 [magicNumber] (2-19)

示例:

const int MAX_PIXEL_COUNT =1000;

    1. 枚举        [enum]
  1. //【重要】枚举前面加上E,命名单词都大写,所有单词间加上“_”,在枚举值最开始需要初始化,枚举值最后加“_MAX”。(1-18

示例:

enum E_CALC_STAGE

{

   E_CALC_STAGE_START =0,

   E_CALC_STAGE_COLLECT_DATA,

   E_CALC_STAGE_DOING,

   E_CALC_STAGE_FINISHED,

E_CALC_STAGE_MAX,

}

    1. 函数
  1. //【重要】函数每个单词的首字母大写其他小写。(1-18) 对函数名的判断,待检查TODO 3333    [fn_name]

示例:

void AddData(int iData);

  1. //【一般】函数最前面不要使用下划线。  (1-21)
  2. X【一般】全局函数的名字使用动词加名词

示例:

void AddData(int iData);

  1. X【一般】成员函数使用动词即可

示例:

data->Add();

  1. //【重要】类的名字由大写字母开头的单词组成。(1-21) [className]

示例:

Class KRecvData

{

……

}

  1. //【重要】类名最前面不加C,规定以“K”开头。(1-21)  [className]

示例:

class K2DImgWin : public KImgWin

{

……

}

  1. //【一般】避免包含继承而来的基类名字。(1-21)[className]

示例:

    错误示例

Class CreatureAnimal: public Creatrue

{

……

}

正确示例

Class KAinimal: public KCreatrue

{

……

}

    1. 文件命名
  1. //【重要】文件名字由大写字母开头的单词组成。(1-15)只检查前两个字母    [filename]

示例:

KRecvData.h

KRecvData.cpp

  1. 【重要】使用宏定义表达式时,使用完备的括号

示例:

#define RECTANGLE_AREA( a, b ) ((a) * (b))

  1. 【重要】将宏所定义的多条表达式放在大括号中

示例:

#define INTI_RECT_VALUE( a, b ) \

{ \

a = 0; \

b = 0; \

}

  1. 函数设计
    1. 返回值
  1. 【重要】函数必须声明返回值类型,如果没有返回值,则为void类型

示例:

float GetValue();

void SetValue(int iWidth, int iHeight);

  1. 【一般】函数正常值用输出参数获得,错误标志用返回值。

示例:

bool GetChar(char* pChar);

  1. 【重要】对于指针/引用方式传递的函数返回值如果是只读用途,需要使用const修饰,以防止被意外修改

示例:

const ClassA& Function();

    1. 参数
  1. 【重要】函数参数要同时声明类型和名称

示例:

void Function(int iVarOne, char cVarTwo);

  1. 【重要】只读用途函数参数,需要使用const修饰,以防止被意外修改

示例:

void Function(const ClassA& objectA);

  1. 【一般】避免将函数的参数作为工作变量以防止内容被意外修改

示例:

void sum_data(int* pSum, unsigned int uiNum, int* pData)

{

//创建局部变量作为工作变量,以防止sum被意外修改

int i_sum_temp = 0;

for (unsigned int i = 0; i < uiNum; i++)

{

i_sum_temp += pData[i];

}

//局部变量完成工作后,再赋值给sum

*pSum = i_sum_temp;

}

  1. 【一般】避免使用类型和数目不确定的参数

示例:

//避免设计类似下面的函数参数

int printf(const chat* pFormat[, argument]…);

  1. 【重要】避免函数有太多的参数,参数个数尽量控制在5个以内
    1. 函数体
  2. 【重要】任何不会修改数据成员的函数都应该声明为const 类型。

示例:

class Stack

{

public:

void Push(int iElem);

int Pop();

int GetCount() const; // const 成员函数

private:

int m_iNum;

int m_iData[100];

};

int Stack::GetCount() const

{

++ m_iNum; // 编译错误,企图修改数据成员m_num

Pop();      // 编译错误,企图调用非const 函数

return m_iNum;

}

  1. 【优先】在函数体的“入口处函数输入数据的有效性进行检查包括参数数据和非参数数据(函数体内使用的全局变量、数据文件等)

示例:

bool ProcessFunc(const unsigned char* pParamIn, unsigned char* pParamOut)

{

// 入口处使用断言判定参数的有效性,且最好每次只判断一个条件

KASSERT(NULL != pParamIn);

KASSERT(NULL != pParamOut);

//使用断言判定全局变量的合法性;

KASSERT(0 < g_iNumVar);

bool b_ret = true;

……

return b_ret;

}

  1. 【一般】在函数体的“出口处对返回值的有效性进行检查

示例:

int GetCurrentNum(int iBegin, int iEnd, int iOffset)

{

int i_cur_num = 0;

i_cur_num = iBegin + iOffset;

//使用断言判定返回值的有效性;

KASSERT(i_cur_num <= iEnd);

return i_cur_num;

}

  1. 【重要】函数完成的功能要单一,符合“单一职责”原则。

示例:

更改密码的处理流程,分成两个功能单一的函数,而不是写在同一个函数中。

bool ValidateOldPassword(const String strOldPwd)

{

// 验证用户输入的旧密码的合法性

……

}

bool SetNewPassword(const String strNewPwd)

{

// 设置用户输入的新密码

……

}

  1. 【一般】尽量不要编写依赖于其他函数内部实现的函数,减少函数之间的耦合

示例:

如下是在DOS下TASM的汇编程序例子。过程Print_Msg的实现依赖于Input_Msg的具体实现,这种程序是非结构化的,难以维护、修改。

... // 程序代码

proc Print_Msg // 过程(函数)Print_Msg

... // 程序代码

jmp LABEL

... // 程序代码

endp

proc Input_Msg // 过程(函数)Input_Msg

... // 程序代码

LABEL:

... // 程序代码

endp

  1. 【一般】尽量避免函数的“记忆功能,相同的输入产生相同的输出

示例:

unsigned int IntegerSum(unsigned int uiBase)

{

// 注意,使用static类型局部变量使函数返回值不可预测

static unsigned int s_ui_sum = 0;

for (unsigned int i = 1; i <= uiBase; i++)

{

s_ui_sum += uiIndex;

}

return s_ui_sum;

}

  1. 【优先】编写可重入函数时,若使用全局变量或静态变量,则应通过关中断、信号量(即PV操作)等手段对其加以保护。

示例:

unsigned int Example(int iParam)

{

unsigned int ui_temp;

// 若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程须等待其释放信号后,才可继续执行。若申请的到信号,则可继续执行,但其它进程必须等待本进程释放信号后,才能再使用本信号

[申请信号量操作]

Exam = iParam;

ui_temp = SquareExam();

[释放信号量操作]

return ui_temp;

}

  1. //【重要】尽量减小函数体的体量,以提高代码的可读性,建议单个函数体不超过80行。
  2. 【优先】禁止使用return 语句返回指向“栈内存”的指针或引用。

示例:

char* GetString()

{

char c_arrary[] = "hello world";

// 错误,函数运行结束时p数组将被销毁

// 编译器将提出警告

return c_arrary;

}

  1. 基本语句
    1. if语句
  1. 【重要】不可将布尔型变量直接与TRUE和FALSE或者0、1比较。
  2. 【重要】将整形变量用“==”或“!=”直接与0比较。

示例:

      int i_value = 121;

      不可模仿布尔变量的风格

      if(!i_value)

      if(i_value)

  1. 【重要】不可将浮点变量用==或!=与任何数字比较
  2. 【一般】浮点变量的精度不能一概而论,根据具体业务定义
  3. 【重要】应当将指针变量用“==”或者“!=”与NULL比较而不是与0比较。
  4. 【一般】if语句“==”比较时,常量写在左边,变量写在右边。

示例:

if(0 == i_value)

  1. 【一般】if语句中不要直接用函数与变量比较,应该将函数返回值与变量比较。

示例:

int i_index = GetIndex();

if(0 == i_index)

{

  ……

}

  1. 【一般】多个if语句判断加括号区分

示例:

const int MAX_DATA = 100;

const int MIN_DATA = 1;

if((i_data > MIN_DATA) && (i_data < MAX_DATA))

{

  ……

}

    1. switch语句
  1. 【优先】每一个case语句结尾不要忘记加break。
  2. 【优先】即使程序不需要default处理,也要保留default:break,每一个case都加上{}

示例:

switch(e_value)

{

    case E_VALUE_ONE:

    {

      ……

      break;

}

    case E_VALUE_TWO:

    {

      ……

      break;

}

    case E_VALUE_THREE:

    {

      ……

      break;

}

default :

    {

      ……

      break;

}

}

  1. 【一般】switch语句的case不能大于10个,否则使用表驱动。

示例:

// 根据关键字,执行处理函数

bool TableDrive::HandleKeyword(int iKeyWord)

{

typedef map<int, PHandle>::const_iterator CI;

CI iter = m_KeyToHandle.find(iKeyWord);

// 没有搜索到关键字

if (m_KeyToHandle.end() == iter)

{

printf("\n @@ search Keyword %d fail!\n", iKeyWord);

return false;

}

// :TRICKY: 注意成员函数指针的引用格式

PHandle p_function = iter->second;

return (this->* p_function)();

}

bool TableDrive::MapKeyToHandle()

{

m_KeyToHandle[KEYWORD_A] = &TableDrive::HandleKeyA;

m_KeyToHandle[KEYWORD_B] = &TableDrive::HandleKeyB;

m_KeyToHandle[KEYWORD_C] = &TableDrive::HandleKeyC;

return true;

}

    1. for语句
  1. 【一般】不可在循环体内修改循环变量。
  2. 【一般】for语句的循环控制变量的取值采用“半开半闭区间”写法

示例:

const int MAX_DATA = 5;  

for(int i = 0; i < MAX_DATA; i++)

{

……

}

  1. 内存管理
    1. 内存使用
  1. 【重要】原则上内存是一项很宝贵的资源,应该尽量节约使用,或重复利用
  2. 【重要】一般而言,对于占用空间较小、使用频繁且生命周期固定的变量,建议使用内存而对于占用空间太大或者生命周期比较复杂的变量,需要使用堆内存

示例1:

//栈上分配内存

char c_id_array[3];

c_id_array [0] = ssid[i+2];

c_id_array [1] = ssid[i+3];

c_id_array [2] = '\0';

示例2:

//堆上分配内存

//占用空间大的内存分配

char* p_mark_string = new char[MAX_MARK_LEN];

memset(p_mark_string, 0, MAX_MARK_LEN);

//生命周期复杂的对象new在堆上

KCinePlaybackState* p_state = new KCinePlaybackStateManual(

m_pCinePlaybackController, dispModeContext->GetCinePlayer());

KASSERT(NULL != p_state);

m_pCinePlaybackController->SetCurState(p_state);

  1. 【重要】如果一个指针/引用在多个类中使用,需要统一维护该内存的生命周期

示例:

KBackEndSubSystem::KBackEndSubSystem()

{

//显示模式上下文指针,初始化

m_pDispModeContext = new KDispModeContext(this, m_Layout);

……

}

KBackEndSubSystem::~KBackEndSubSystem()

{

//该指针在后端图像系统的很多类中使用,但是统一由所属的后端子系统维护和管理内存;

RELEASE_POINTER(m_pDispModeContext);

……

}

  1. 【优先】所有变量使用前都要进行初始化

示例1:

ClassA* p_obj_a = new ClassA();

示例2:

char* p_buf = new char[MAX_LENGTH];

memset(p_buf, 0, MAX_LENGTH);

  1. 【优先】类的构造函数要对所有数据成员初始化且建议使用初始化表

示例:

class F

{

public:

F(int x, int y); // 构造函数

private:

int m_x;

int m_y;

}

F::F(int x, int y)

: m_x(x), m_y(y)  //初始化表

{

……

}

  1. 【优先】类的初始化时,如果存在继承关系,派生类必须在其初始化表里调用基类的构造函数。

示例:

class A

{…

A(int x); // A 的构造函数

};

class B : public A

{…

B(int x, int y);// B 的构造函数

};

B::B(int x, int y)

: A(x) // 在初始化表里调用A 的构造函数

{

}

  1. 【重要】对于含有指针成员的类,要注意确认是否需要重写类的构造函数、拷贝构造函数拷贝赋值函数等。
  2. 【优先】指针类型的变量在使用前一定记得是否为NULL,避免使用空指针

示例:

KASSERT(m_pContext != NULL);

KASSERT(m_pContext->Get2DImg() != NULL);

m_pContext->Get2DImg()->Show();

  1. 【优先】防止内存操作越界

示例1: 

void RecordValue(const int iIndex, const unsigned long ulValue)

{

const MAX_ARRAY_NUM = 100;

static unsigned long s_ul_array[MAX_ARRAY_NUM];

……

//使用断言防卫数组越界

KASSERT(0 <= iIndex)

KASSERT(MAX_ARRAY_NUM > iIndex);

s_ul_array [iIndex] = ulValue;

……

}

示例2:

void ProcessFunction(unsigned char* pSrc)

{

char c_buffer[MAX_LENGTH] = {0};

//内存数据的拷贝,注意不要越界

memcpy(c_buffer, pSrc, sizeof(c_buffer));

……

}

  1. 【优先】多线程对内存数据进行并发访问前,记得要加锁保护

示例:

void KThread::AddThreadToMap(unsigned int uiTid)

{

    // 加入Map

    KThreadInfo info = {this, 0};

    g_threadMapMutex.Lock(); // 加锁

g_threadMap.insert(std::pair<unsigned int, KThreadInfo>( uiTid, info));

    g_threadMapMutex.Unlock();   // 解锁

}

    1. new/delete
  1. 【优先】提倡使用new/delete操作内存避免使用malloc/free
  2. 【优先】new/delete内存申请与释放必须配对使用

示例:

ClassA *p_obj_a = new ClassA;

……

delete p_obj_a;

p_obj_a = NULL;

  1. 【优先】对于对象数组,需要在new/delete 中配对使用”[]

示例:

char* p_mark_str = new char[MAX_MARK_LEN];

……

delete []p_mark_str;

p_mark_str = NULL;

  1. 【优先】释放内存后要立即将指针置为NULL,防止产生野指针

示例:

#define RELEASE_POINTER(p) \

if(p) \

{ \

delete p; \

p = NULL; \

}

  1. 【重要】对于频繁使用的堆内存,尽量避免重复new和delete,以避免内存碎片低效率

  1. 异常处理
    1. 断言
  1. 【优先】编码过程中要求使用防卫式编程方法
  • 子程序应该不因传入数据错误而受到破坏,例如,使用空指针、数组越界,导致程序发生段错误
  • 对于编写函数所基于的任何假定,都应该使用断言等手段进行防卫,保证程序的安全
  • 防止垃圾数据进入,同时保证产生垃圾数据
  1. 【优先】断言使用公司自定义的KASSERT
  2. 【优先】断言仅是对程序安全性检查和保证,禁止在其中执行必要的业务逻辑

示例:

bool KBackEndSystem::SaveCineFile()

{

DataTree tree("BackEndSystem");

int i_probe_id = KProbeManager::GetInstance()->GetCurProbeId();

//错误必要执行的业务流程放在了断言中,如果release版本中关闭断言的

//就会导致业务流程的缺失,造成严重的错误

    KASSERT(tree.AddElement("ProbeId",    i_probe_id));

    KASSERT(tree.AddElement("Row",     m_pWindowSwitchContrl->RowNum()));

    KASSERT(tree.AddElement("Column",   m_pWindowSwitchContrl->ColNum()));

KASSERT(tree.AddElement("TGCStatus", m_bTGCShowStatus));

……

}

  1. 【优先】使用指针时,要用断言进行判空处理

示例:

KASSERT(NULL != p_value);

  1. 【优先】多个参数合法性的判断,要分开使用断言

示例:

KASSERT(b < a);

KASSERT(b > c);

  1. 【优先】函数的入参用断言进行合法性判断。

示例:

const PdbFracFilterPara& GetFracFilterPara(int iLevel=0) const

{

KASSERT(iLevel < m_nNumOfFracFilterPara);

KASSERT(iLevel >= 0);  

……

return m_pdbSysCommon.fracfilterpara(iLevel);

}

  1. 【重要】函数返回值,如果非预期, 则直接用断言对此返回值进行合法判断。

示例:

     const int MAX_DATA = 100;

int i_data = GetData();

KASSERT (i_data < MAX_DATA);

  1. 【重要】函数的出参,如果非预期, 则直接用断言对此返回值进行合法判断。

示例:

const int MAX_DISTANCE = 15;

int i_distance;

CalcDistance(&i_distance);

KASSERT(i_distance < MAX_DISTANCE);

  1. 【重要】参数除法计算,断言校验除0等异常情况。

示例:

float f_x;

float f_y;

KASSERT(f_x <= EPSINON);

KASSERT(f_x >= -EPSINON);

float f_ratio = f_y / f_x;

    1. 异常提示
  1. 【优先】与外部系统通信出错给出异常提示。

示例:与外部系统进行dicom通信错误给出提示

void DcmCmdSender::Run()

{

  …

if (DCM_CMD_TYPE_MPPS == str_cmd_type)

{

     i_ret = SendMppsCommand(strCmdUID, strErrMsg);

   }

//组合命令执行结果

    mapDcmCmdState[DCM_FIELD_QUEUE_STATUS] = QUEUE_STATUS_FINISH;

//队列状态:已结束

if (ERR_OK == i_ret)

{

//命令状态:成功,结果:成功

             ……

}

else

{

//命令状态:失败,结果:错误信息

    LOGW(“send mpps error,%s”, strErrMsg);

}

}

  1. 【优先】设备状态出错,给出异常提示

示例:超声设备运行过程中,断网给出异常提示

NET_STATE GetNetWorkStatus()

{

E_WIFI_STATE e_wifi_state;

  …

  // whether wifi is plug in and enabled

  if((e_wifi_state == E_WIFI_STATE_NOT_EXIST)

|| (e_wifi_state == E_WIFI_STATE_NOT_UP))

{

LOGW("network is down");

return NET_STATE_ETH_UNCONNECT;

}

  return NET_STATE_WIFI_AVAILABLE;

}

  1. 【优先】读取文件异常,给出异常提示

示例:加载回放电影异常,给出错误提示

bool KBackEndSystem::LoadCineFile(KCineFileType::Type eCinetype,

const char* pFilePath,

KCineFileLoadResult::Result& eErrCode)

    ……

DataTree tree("BackEndSystem");

    string str_path = string(pFilePath) + "_backendsystem.idx";

b_status &= tree.LoadDataFromFile(str_path.c_str());

if(!b_status)

{

      LOGW("Load Cine File err,errcode:%d", b_status);

}

……

     return b_status;

  1. 【优先】图像数据上传解析异常,给出提示。

示例:PW模式下,解析数据出错,给出错误提示

bool KPWImgPacketParse::ParseImgData(unsigned char* pData)

{

……

   KPWFrameData*  p_frame_data  = mImgFramBuf.GetBuffer(1);

if (NULL == p_frame_data)

{

    LOGW("KImgPacketParse: PW parse and preprocess lost one frame");

return false;

}

……

return true;

}

    1. 日志
  1. 【优先】所有工程,打印统一接口,模块调试完成,关闭打印日志
  2. 【优先】一般的调试信息,不要打印日志,以免程序影响效率。
  3. 【优先】对于频繁调用接口,接口里面不要加打印日志。
  4. 【重要】日志要分级,一般情况下,日志中只记录警告信息和错误信息

示例:

//执行sql语句

int DbSqlite::Exec(const char* pSql)

{

     ……

   i_Ret = sqlite3_exec(m_pDatabase, str_Sql.c_str(), NULL, NULL, p_err_msg);

     ………

if (NULL != p_err_msg)

{

……

sqlite3_free(p_err_msg);

LOGW("Failed to exec sql: %s ", str_Sql.c_str());

}

       ……

}

  1. 【重要】程序运行的关键参数需要记录在日志中,便于后续协助定位程序异常。

示例:

void KeyboardBrightAutoSense::Init()

{

     ……

    i_base_bright = KAdjustAudioBright::ReadDefaultBrightFromEEPROM();

LOGI("i_base_bright = %d", i_base_bright);

}

    1. 组合方式
  1. 【优先】对于预料的错误,直接给提示错误码和错误原因,并在日志文件中记录,程序继续执行。

示例:

DICOM传输过程中的错误码定义

#define PATIENT_ERR_CODE(code) (-((MODULE_PATIENT << 8) | (code)))

//不支持该操作

const int ERR_NOT_SUPPORT = PATIENT_ERR_CODE(0x01);

//无效的UID

const int ERR_INVALID_UID = PATIENT_ERR_CODE(0x02);

//操作被用户取消

const int ERR_USER_CANCEL_OPERATION = PATIENT_ERR_CODE(0x03);

   ……

//构造数据集失败

const int ERR_BUILD_DATASET_FAIL = PATIENT_ERR_CODE(0xB5);  

//填充数据集失败

const int ERR_FILL_DATASET_FAIL = PATIENT_ERR_CODE(0xB6);  

//加载DICOM文件失败

const int ERR_LOAD_DICOM_FILE_FAIL = PATIENT_ERR_CODE(0xB7);

//加载DICOM图像失败

const int ERR_LOAD_DICOM_IMAGE_FAIL = PATIENT_ERR_CODE(0xB8);

//Dicom文件不存在

const int ERR_DICOM_FILE_NOT_EXIST =  PATIENT_ERR_CODE(0xB9);  

int DcmImageExtractor::SaveAsCine(const std::string& strFilePath,

const std::string& strCineFormat,

const bool& bIsCancel)

{

if (!m_bFileLoaded)

{

LOGE("dcm file load failed!");

return ERR_LOAD_DICOM_FILE_FAIL;

         }

         ……

}

  1. 【优先】对于非预期的错误,并在日志文件中记录,程序退出。

示例:

const int MAX_DISTANCE = 28;

int i_data = GetCurDistance();

if(i_data > MAX_DISTANCE)

{

   LOGE(“cur distance is out of range”)

   return;

}

  1. 程序效率
    1. 原则
  1. 【重要】在满足正确性、可靠性、健壮性和可读性等质量因素的前提下,提高程序效率
  2. 【重要】提高程序的全局效率为主,局部效率为辅,不能为了提高局部效率而降低程序的全局效率
  3. 【一般】先确定程序的效率瓶颈”,再着手进行效率优化。
  4. 【重要】除非能够确定某处代码是程序的“效率瓶颈,否则不能为了追求效率的优化而影响代码的可读性
  5. 【一般】先优化数据结构和算法,再优化执行代码
    1. 措施
  6. 【重要】尽量充分利用硬件资源,一般使用硬件可以完成的工作,尽量使用硬件而不是软件
  7. 【重要】多线程使用时,尽量减小占用CPU资源;对于空闲的线程,优先选用通知激活方式,避免选用线程空转;对于只使用一次的线程,尽量复用现有线程
  8. 【一般】通常情况下需要对内存进行对齐,提高访问效率;如果对空间效率要求比较高,可以不进行对齐

示例:

#pragma pack(4)   //指定4字节对齐

  1. 【重要】尽量减小变量的作用,在真正使用该变量时才进行声明和初始化

示例:

// 不要在程序起始处或者变量使用之前声明和初始化

// int i_index = 0;

// bool b_cond = true;

…… // do something

// 在真正开始使用的地方创建

bool b_cond = true;

for(int i_index = 0; i_index < MAX && b_cond; i_index++)

{

……

}

  1. 【重要】函数的返回值和参数,如果能够使用“引用传递方式,尽量避免使用“值传递

示例:

String& String::operate=(const String& strOther);

注意:该条目不适用于基本数据类型、STL迭代器和函数对象;

  1. 【一般】尽量少做转型动作

示例:

对于继承于Window的SpecialWindow:

typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

VPW vct_win_ptrs;

……

for (VPW::iterator iter = vct_win_ptrs.begin();

iter != vct_win_ptrs.end(); ++iter)

{

if(SpecialWindow* p_sw = dynamic_cast<SpecialWindow*>(iter->get()))

{

p_sw->blink();

{

}

直接使用派生类的指针会更好一些:

typedef std::vector<std::tr1::shared_ptr< SpecialWindow>> VPSW;

VPSW vct_win_ptrs;

……

for (VSPW::iterator iter = vct_win_ptrs.begin();

iter != vct_win_ptrs.end(); ++iter)

{

(*iter)->blink();

}

  1. 【重要】尽量减少循环嵌套层次谨慎使用超过两层循环嵌套
  2. 【一般】在多层循环中,尽量将最忙的循环放在最内层以减少CPU跨切循环层的次数

示例:

const int ROW_NUM = 100;

const int COL_NUM = 5;

for (int i = 0; i < ROW_NUM; i++)

{

for (int j = 0; j < COL_NUM; j++)

{

i_sum += getAddValue(i, j);

}

}

可以改为如下方式,以提高效率 

for (int j = 0; j < COL_NUM; j++)

{

for (int i = 0; i < ROW_NUM; i++)

{

i_sum += getAddValue(i, j);

}

}

  1. 【重要】合理安排循环和判断语句的层次结构,尽可能将判断语句放在循环体之外

示例:

for (int i = 0; i < MAX_RECT_NUMBER; i++)

{

if (eDataType == RECT_AREA)

{

i_area_sum += i_rect_area[i];

}

else

{

i_rect_length_sum += rect[i].m_iLength;

i_rect_width_sum  += rect[i].m_iWidth;

}

}

因为判断语句与循环变量无关,故可以进行如下改进,以减少判断次数:

if (eDataType == RECT_AREA)

{

for (int i = 0; i < MAX_RECT_NUMBER; i++)

{

i_area_sum += i_rect_area[i];

}

}

else

{

for (int i = 0; i < MAX_RECT_NUMBER; i++)

{

i_rect_length_sum += rect[i].m_iLength;

i_rect_width_sum  += rect[i].m_iWidth;

}

}

  1. 【重要】循环体内工作量最小化

示例:

for (int i = 0; i < MAX_ADD_NUMBER; i++)

{

i_sum += i;

//以下语句放到循环体外更高效

//i_back_sum = i_sum;

}

i_back_sum = i_sum;

  1. 【重要】对于循环体内重复进行的运算,考虑是否可以提取到循环体外完成

示例:

for(int i = 0; i < GetMaxCount(iArray); i++)

{

……

}

可以把循环次数的计算提取到循环体外:

int i_cnt = GetMaxCount(iArray);

for (int i = 0; i < i_cnt; i++)

{

……

}

  1. 【一般】通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。

示例:

typedef unsigned char BYTE;

typedef unsigned short WORD;

typedef struct StudentScoreStru

{

BYTE m_cName[8];

BYTE m_cAge;

BYTE m_cSex;

BYTE m_cClass;

BYTE m_cSubject;

float m_fScore;

} StudentScore;

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

typedef struct StudentStru

{

BYTE m_cName[8];

BYTE m_cAge;

BYTE m_cSex;

BYTE m_cClass;

} Student;

typedef struct StudentScoreStu

{

WORD m_usStudentIndex;

BYTE m_cSubject;

float m_fScore;

} StudentScore;

  1. 其他
  1. 【重要】头文件中尽量不引用其他头文件,以降低文件间的编译依存关系

示例:

//#include “KPWImgWin”

//#include “IFePWParamSet”

//不使用头文件的引用,改为对应类的前置声明即可

class KPWImgWin;

class IFePWParamSet;

class IPWImgWinFacade : public IImgWinFacade

{

public:

……

private:

    const KBeDParamPack*  m_pBeParam;

    const IFePWParamSet*  m_pFeParam;

};

  1. 【优先】头文件中#include其他头文件时,使用预编译语句#pragma once进行限定,以提高编译效率

示例:

#pragma once

#include “KObject.h”

  1. 【优先】使用#include包含头文件时,不要包含文件的路径避免对目录结构依赖。

示例:

#include “./include/HeaderFile.h”

直接使用文件名即可

#include “HeaderFile.h”

  1. 【重要】头文件中尽量避免对using namespace的使用,以减少命名空间的扩散

示例:

using namespace std::tr1;

shared_ptr<Investment> m_pInvestment;

改为使用完整命名

std::tr1::shared_ptr<Investment> m_pInvestment;

  1. 【重要】尽量减小文件内容长度建议单个文件不超过1200行代码为宜,配置文件超过1200行需要特殊说明。
  2. 【一般】避免编写技巧性很高的代码,以提高代码的可读性

示例:

*i_stat_poi++ +=1;

 改写成

*i_stat_poi +=1;

  i_stat_poi++;

  1. 【重要】当心变量发生上溢或下溢

示例:

char c_letter = 127;

c_letter++; //上溢

char c_letter = -128;

c_letter--; 下溢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_21239475

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值