C/C++ 代码规范

代码可读性

命名规范

函数命名规则(驼峰命名法)

普通函数的函数名由若干个单词组成,第一个单词全部小写,第二个单词开始首字母大写。

1
2
3
bool getMeanValue(...);	
int csvToShp(...);
double** computeUrbanConversionMatrix(...);

a. 如果是 inline 类型的函数,在函数名前加下划线 _

1
inline int _getCuberInterpolationValue(…);

b. 如果是 static 类型的函数,函数名第一个单词首字母大写

1
static int OpenFiles(…);

变量命名规则(匈牙利命名法)

  • 原则 1:禁止使用简单英文单词命名变量,如 min,max,left,right,会造成和某些库保留变量名的冲突
  • 原则 2: 变量作用域(w/m/无) + 指针/数组(p/pp/ppp/无) + 变量类型(c/u/n/f/d/s/v 等) + 变量名称
  • 原则 3:迭代变量允许但不推荐 i,j,k,m,n。但尽量使用有意义的迭代变量名称,如: _nFilesIdx, _nUser_inMember_i

表 1 变量作用域命名规则

变量作用域 前缀 例子 意义
全局变量 w wnValue 全局 int 型变量
静态变量 S (大写) SnValue 静态 int 型变量
类变量 m mnValue 类 int 型变量
普通变量 不需要 nValue int 型变量
临时变量 _ _nValue 临时 int 型变量

表 2 指针/数组命名规则

指针/数组 前缀 例子 意义
一维 p pdValues 普通一维 double 型数组
二维 pp ppdValues 普通二维 double 型数组
三维 ppp pppdValues 普通三维 double 型数组
非指针 dValue 普通 double 型对象

表 3 变量类型命名规则

变量类型 前缀 例子 意义
char c cValue
unsigned char, byte u uValue
short, unsigned short n nValue
int, unsigned int
long, long long l lValue
float f fVafue
double d dVadue
bool b bVabue
char*, string s / str / sz strValue
list, vector v vValues 链表/容器
file in/out/fi inFile / outFile / fiOutput 输入输出文件
map, hash map mapKeyValues 映射表/哈希表

整洁、对齐

  • 错误范例
1
2
3
4
5
6
7
8
9
10
int main(int argc, char* argv[])
{
int nRow=5;//数组行数
int nCol=5;//数组列数
int nValue=3;//数组元素值
//创建二维指针,初始化数组数据
double **ppdData = new *double[nRow];
for(int i=0;i<nRow;i++){ppdData[i]=new double[nCol];memset(ppdData[i],0,sizeof(double)*nCol);}
return 0;
}
  • 修改

  运算符与变量之间空格;;,等表示间隔的符号后面空格;语句之间换行;代码片段之间空格;注释中,中英文之间尽量空格;注释对齐;缩进对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char* argv[])
{
int nRow = 5; //数组行数
int nCol = 5; //数组列数
int nValue = 3; //数组元素数

// 创建二维指针,初始化数组数据
double **ppdData = new *double[nRow];
for(int i = 0; i < nRow; i++){
ppdData[i] = new double[nCol];
// 数组元素赋值为 3
memset(ppdData[i], 3, sizeof(double)*nCol);
}

return 0;
}

编写注释

  • 精简:没有太大用处的不写,能从代码直接看出含义的不写,类中的getset方法不写
1
2
3
// The first String is student's name
// The Second Integer is student's score
std::map<std::string, int> mapScoreMap;
1
2
// Student' name -> Student's score
std::map<std::string, int> mapScoreMap;
  • 不因为写了注释就给变量随意取名;
  • 通过注释来记录当前解决方案的算法过程,使得读者易于理解;
  • 注释用于提醒一些特殊情况;
标记 用法
TODO 待做
FIXME 待修复
HACH 粗糙的解决方案
XXX 危险!这里有重要问题
1
2
3
4
5
6
7
8
//TODO: 设置源和目的地块适宜性(需要外部读入,是否自己输入这部分还要修正)
//TODO: 添加外部地块适宜性输入代码,pSuitability需要和mvStoreNames匹配
//TODO: load attributes from table
for (int i=0; i<mvOrigLandParcels.size(); i++){
mvOrigLandParcels[i].pdSuitability = new double[mnStoreCount];
for (int j=0; j<mnStoreCount; j++)
mvOrigLandParcels[i].pdSuitability[j] = 0.0f;
}
  • 添加测试用例来说明;
1
2
3
4
5
//...
// Example: add(1, 2), return 3
int add(int x, int y) {
return x + y;
}
  • 在很复杂的函数调用中对每个参数标上名字
1
2
3
int a = 1;
int b = 2;
int num = add(/* x = */ a, /* y = */ b);
  • 对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面;
  • 函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数、表)等;
1
2
3
4
5
6
7
8
/*************************************************
Description: 对一个数组中的元素求和
Parameters:
pnData : 数据指针,int *
n : 数组长度, int
Return: // 数组元素之和,int
*************************************************/
int arrSum(int *pnData, int n);
  • 说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明;
1
2
3
4
5
6
7
8
9
10
11
12
/************************************************************
FileName: SparseAutoencoder.h
Author: liuphhhh
Version : 1.0
Date: 2018-03-20
Description: // 模块描述
Function List: // 主要函数及其功能
1. -------
History: // 历史修改记录
<author> <time> <version > <desc>
liuphhhh 18/03/20 1.0 build this moudle
***********************************************************/
  • 注释量一般不低于20%。(要求不低于30%

拆分长表达式

拆分前

1
if (split(line, ',')[0].strip() == "SYSU" && split(line, ':')[1].strip() == "GIS")

拆分后

1
2
sList = split(line, ':');
if (sList[0].strip == "SYSU" && sList[1].strip() == "GIS")

减小变量的作用域

  • 尽量不使用全局变量
  • 作用域越小,越容易定位到变量所有使用的代码区间,这在动态类型的语言(例如Python,Javascript)中尤为重要。

任务分解,函数抽取

  • 大问题拆分成小问题。首先应该明确一个函数的高层次目标,然后对于不是直接为了这个目标工作的代码,抽取出来放到独立的函数中。
  • 并非函数抽取的越多越好,如果抽取过多,在阅读代码的时候可能需要不断跳来跳去。
  • 函数抽取也用于减小代码的冗余。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 问题:
// 找出10个向量中最相似(余弦值最大)的两个向量之间的余弦距离
// 假设所有向量存储在一个二维数组中,每一行为一个向量(的转置),每一个向量为5维,那么存储向量的数组应当为10 × 5 大小


double findMostSimilarVectors(double **ppdVec, int nVecNum = 10, int nDim = 5)
{
// 初始化向量之间余弦值的最小值
double _dCos = -1;
for(int i = 0; i < nVecNum-1; i ++)
for(int j = i+1; j < nVecNum; j ++)
{
// 计算两个向量之间的余弦值;
double _tmpDis = calCosine(ppdVec[i], ppdVec[j]);
if(_tmpDis > _dCos) _dCos = _tmpDis;
}

return _dCos;
}

double calCosine(double *pdV1, double *pdV2)
{
//...
}

No过度设计

编码过程会有很多变化,过度设计的内容到最后往往是无用的。确保代码的rubust,模块分解需要适度,太零碎的模块不易于整合。

例如以上计算两个向量之间最小的余弦距离,没必要再另外写一个查找最小值的函数。


Coding之前整理逻辑,书写伪代码

先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。

Algorithm Bagging.

Input:

  • D: a set of d training tuples
  • k: the number of models in the ensemble
  • M: a classification learning scheme(decision tree algorithm, naive Bayesian, etc.)

Output: the ensemble—a composite model M

Method:

  1. for i = 1 to k do //create k models
  2. create bootstrap sample D_i, by sampling D with replacement
  3. use D_i and the learning scheme to derive a model M_i
  4. endfor

指针内存管理

一维指针

为防止内存泄漏造成程序不稳定,在类和函数中声明的指针一定要先置 NULLnullptr;每次使用前先判断是否为 NULL,若需要更新先 deletenew;并且在类的析构函数中或函数尾部对该指针 delete 并置 NULLnullptr 处理。

1
2
3
4
5
6
7
8
9
10
// 先判断是否为空
if (mpVals != NULL)
{
// delete 并且置空
delete[]mpVals;
mpVals = NULL;
}
// 再 new
mpVals = new double[5];
memset(mpVals, 0, sizeof(double) * 5);

二维指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int nRows = 10, nCols = 20;
// 创建二维指针
double** _pp = new double*[nRows];
for (int i = 0; i < nRows; i++)
{
_pp[i] = new double[nCols];
memset(_pp[i], 0, nCols * sizeof(double));
}

// 删除
for (int i = 0; i < nRows; i++)
{
delete[]_pp[i];
_pp[i] = NULL;
}
delete[]_pp;
_pp = NULL;

其他