C / C++ 编程规范
2018-12-24 Version 1.0 by Liu Penghua @ SYSU & Yao Yao @ CUG
1. 代码可读性
1.1. 命名规范
1.1.1. 函数命名规则(驼峰命名法)
普通函数的函数名由若干个单词组成,第一个单词全部小写,第二个单词开始首字母大写。
bool getMeanValue(...);
int csvToShp(...);
double** computeUrbanConversionMatrix(...);
a. 如果是 inline 类型的函数,在函数名前加下划线 _
inline int _getCuberInterpolationValue(…);
b. 如果是 static 类型的函数,函数名第一个单词首字母大写
static int OpenFiles(…);
1.1.2. 变量命名规则(匈牙利命名法)
- 原则 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_i
、nMember_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 | n | nValue | |
long, long long | l | lValue | |
float | f | fValue | |
double | d | dValue | |
bool | b | bValue | |
char*, string | s / str / sz | strValue | |
list, vector | v | vValues | 链表/容器 |
file | in/out/fi | inFile / outFile / fiOutput | 输入输出文件 |
map, hash | map | mapKeyValues | 映射表/哈希表 |
1.2. 整洁、对齐
- 错误范例
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;
}
- 修改
运算符与变量之间空格;;
和,
等表示间隔的符号后面空格;语句之间换行;代码片段之间空格;注释中,中英文之间尽量空格;注释对齐;缩进对齐。
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];
// 数组元素赋值为 0
memset(ppdData[i], 0, sizeof(double)*nCol);
}
return 0;
}
1.3. 编写注释
- 精简:没有太大用处的不写,能从代码直接看出含义的不写,类中的
get
、set
方法不写;
// The first String is student's name
// The Second Integer is student's score
std::map<std::string, int> mapScoreMap;
// Student' name -> Student's score
std::map<std::string, int> mapScoreMap;
- 不因为写了注释就给变量随意取名;
- 通过注释来记录当前解决方案的算法过程,使得读者易于理解;
- 注释用于提醒一些特殊情况;
标记 | 用法 |
---|---|
TODO | 待做 |
FIXME | 待修复 |
HACH | 粗糙的解决方案 |
XXX | 危险!这里有重要问题 |
//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;
}
- 添加测试用例来说明;
//...
// Example: add(1, 2), return 3
int add(int x, int y) {
return x + y;
}
- 在很复杂的函数调用中对每个参数标上名字
int a = 1;
int b = 2;
int num = add(/* x = */ a, /* y = */ b);
- 对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面;
- 函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数、表)等;
/*************************************************
Description: 对一个数组中的元素求和
Parameters:
pnData : 数据指针,int *
n : 数组长度, int
Return: // 数组元素之和,int
*************************************************/
int arrSum(int *pnData, int n);
- 说明性文件(如头文件
.h
文件、.inc
文件、.def
文件、编译说明文件.cfg等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明;
/************************************************************
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.4. 拆分长表达式
拆分前
if (split(line, ',')[0].strip() == "CUG" && split(line, ':')[1].strip() == "GIS")
拆分后
sList = split(line, ':');
if (sList[0].strip == "CUG" && sList[1].strip() == "GIS")
1.5. 减小变量的作用域
- 尽量不使用全局变量
- 作用域越小,越容易定位到变量所有使用的代码区间,这在动态类型的语言(例如Python,Javascript)中尤为重要。
1.6. 任务分解,函数抽取
- 大问题拆分成小问题。首先应该明确一个函数的高层次目标,然后对于不是直接为了这个目标工作的代码,抽取出来放到独立的函数中。
- 并非函数抽取的越多越好,如果抽取过多,在阅读代码的时候可能需要不断跳来跳去。
- 函数抽取也用于减小代码的冗余。
// 问题:
// 找出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)
{
//...
}
1.7. No过度设计
编码过程会有很多变化,过度设计的内容到最后往往是无用的。确保代码的rubust,模块分解需要适度,太零碎的模块不易于整合。
例如以上计算两个向量之间最小的余弦距离,没必要再另外写一个查找最小值的函数。
1.8. Coding之前整理逻辑,书写伪代码
先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。
Algorithm Bagging.
**Input:**
D: a set of *d* training tuples
k: the number of models in the ensemble
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**
2. 指针内存管理
2.1. 一维指针
为防止内存泄漏造成程序不稳定,在类和函数中声明的指针一定要先置 NULL
或 nullptr
;每次使用前先判断是否为 NULL
,若需要更新先 delete
再 new
;并且在类的析构函数中或函数尾部对该指针 delete
并置 NULL
或 nullptr
处理。
// 先判断是否为空
if (mpVals != NULL)
{
// delete 并且置空
delete[]mpVals;
mpVals = NULL;
}
// 再 new
mpVals = new double[5];
memset(mpVals, 0, sizeof(double) * 5);
2.2. 二维指针
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;