头文件
#define
保护
所有头文件都应该有#define
保护来防止头文件被多重包含,命名格式是:<PROJECT>_<PATH>_<FILE>_H_。
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
前置声明
使用#include
包含需要的头文件,避免使用前置声明。
#include
路径
项目内头文件应按照项目源代码目录树结构排列,避免使用.
(当前目录)和..
(上级目录)。
#include
顺序
- 源文件相关的头文件,比如:
logging.cc
的头文件logging.h
- C系统文件
- C++系统文件
- 其他库的头文件
- 本项目内的头文件
作用域
命名空间
鼓励使用命名空间,命名空间的名称可基于项目名或路径。禁止使用using
。
匿名命名空间和静态变量
在.cc
中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为static
。
// 声明为static
static int a = 1;
// 使用匿名命名空间
namespace{
int b = 1;
} // namespace
非成员函数、静态成员函数和全局函数
使用静态成员函数或命名空间内的非成员函数,尽量不要用裸的全局函数。
局部变量
将函数变量尽可能置于最小作用域内,并在变量声明时进行初始化。
静态和全局变量
禁止定义全局和静态的对象,原生数据类型是可以的。
类
构造函数的职责
构造函数不允许调用虚函数。不要再无法报出错误时,进行可能失败的初始化。可以考虑添加一个Init()
方法。
构造函数隐式类型转换
在类型定义中,类型转换运算符和单参数构造函数都应当用explicit
进行标记,抑制隐式类型转换。
可拷贝类型和可移动类型
如果你的类型需要,就让他们支持拷贝/移动,否则,就把隐式产生的拷贝和移动函数禁用。
结构体和类
仅当只有数据成员时使用struct
,其他一概使用class
。
继承
尽量使用组合。如果使用继承的话,定义为public
继承。
声明顺序
将相似的声明放在一起,将public
部分放在最前,后跟protected
,最后是private
。
顺序如下:
- 类型定义
- 常量
- 工厂函数
- 构造函数
- 赋值运算符
- 析构函数
- 其他函数
- 数据成员
函数
输入和输出
函数输出倾向于使用返回值,而不是输出参数。返回值使用按值返回,否则按引用返回。避免返回指针,除非它可以为空。
输入参数是值参或const
引用(所有引用传递的参数必须加上const
),输出参数为指针。
排序函数参数时,将所有输入参数放在所有输出参数之前。
编写简短函数
尽量编写简短,凝练的函数。如果函数超过40行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。
函数重载
若要使用函数重载,则必须能让读者一看调用点就胸有成竹,而不用花心思猜测调用的重载函数到底是哪一个。这一规则也适用于构造函数。
缺省参数
尽量不使用缺省参数语法,可以修改成函数重载。
其他
类型转换
使用C++的类型转换,比如:static_cast<>()
。不要使用int y = (int)x
或int y = int(x)
等C风格类型转换方式。
static_cast
替代C风格的类型转换,或某个类指针需要明确的向上转换为父类指针。const_cast
去掉const限定符。reinterpret_cast
指针类型和整型或其他指针之间进行不安全的相互转换。仅在你对所做的一切了然于心时使用。dynamic_cast
确认给定的基类指针指向某个派生类的实例,那么就可以使用dynamic_cast
将基类指针转换成派生类指针。
const
尽可能的情况下使用const
限定符。
宏定义
使用宏时要非常谨慎,尽量使用内联函数、枚举、常量代替。
命名约定
通用命名规则
函数命名,变量命名,文件命名要有描述性,少用缩写。一些广为人知的缩写是允许的。
文件命名
文件名要全部小写,各个单词之间用_
连接。eg:my_useful_class.cc
。
类型命名
类型名称的每个单词首字母均大写,不包含下划线。eg:MyExcitingClass
。类型包括:类,结构体,类型定义(typedef
),枚举,类型模板参数。
变量命名
- 普通变量,一律小写,单词之间用下划线连接。eg:
table_name
。 - 类成员变量,一律小写,单词之间用下划线连接,末尾要接下划线。eg:
table_name_
。 - 结构体变量,和普通变量一样。
常量命名
以”k“开头,后续所有的单词首字母大写。eg:const int kDaysInAWeek = 7;
。
函数命名
函数名的所有单词首字母大写(驼峰变量名),没有下划线。对于缩写单词,也要首字母大写。eg:int DrawRgb()
;
命名空间命名
全部小写,多个单词用下划线连接。
枚举命名
以”k“开头,后续的所有单词首字母大写。eg:
enum UrlTableErrors{
kOk = 0,
kErrorOutOfMemory,
kErrorMalformedInput
};
宏命名
全部大写,单词之间用下划线连接。eg:MY_MACRO_THAT_SCARES_SMALL_CHILDREN
。
注释
采用Doxygen
注释语法。
- 单行注释
///
- 行尾注释
///<
- 多行注释:
/**
*
*/
文件注释
在文件开头加入文件注释。
/**
* @file 文件名
* @brief 简介
* @details 细节
* @mainpage 工程概览
* @author 作者
* @email 邮箱
* @version 版本号
* @date 年-月-日
* @license 版权
*/
类注释
类的注释方式非常简单,使用@brief
,后面填写类的概述,换行填写类的详细描述信息。
/**
* @brief 类的简单概述
* 类的详细概述
*/
命名空间、结构体、联合体、枚举定义与类注释方式一致。
函数注释
简约注释
函数注释主要包含函数简介@brief
、参数说明@param
、返回说明@return
、返回值说明@retval
。
/**
* @brief 函数简介
*
* @param 形参 参数说明
* @param 形参 参数说明
* @return 返回说明
* @retval value1 返回值说明
* @retval value2 返回值说明
*/
详细注释
可以根据需要添加详细说明@detail
、注解@note
、注意@attention
、警告@warning
、异常@exception
等。
/**
* @brief 函数简介
* @detail 详细说明
*
* @param 形参 参数说明
* @param 形参 参数说明
* @return 返回说明
* @retval value1 返回值说明
* @retval value2 返回值说明
* @note 注解
* @attention 注意
* @warning 警告
* @exception 异常
*/
变量注释
代码前注释
/// 缓存大小
int buf_size = 1024;
代码后注释
int buf_size = 1024; ///< 缓存大小
其它
下面一些标注可以根据需要选择使用
标注 | 说明 |
---|---|
@see | 参考 |
@class | 引用类,用于文档生成链接 |
@var | 引用该变量,用于文档生成链接 |
@enum | 引用枚举,用于文档生成链接 |
@code | 代码块开始,与@endcode成对使用 |
@endcode | 代码块结束,与@code成对使用 |
@bug | 缺陷,链接到所有缺陷汇总的缺陷列表 |
@todo | TODO,链接到所有TODO汇总的TODO列表 |
@example | 使用例子说明 |
@remarks | 备注说明 |
@pre | 函数前置条件 |
@deprecated | 函数过时说明 |
格式
行长度
每一行代码字符数不超过80个,不强制。
空格还是制表位
只使用空格,缩进采用2个空格。
函数声明和定义
- 返回类型和函数名在同一行。
- 参数也尽量和函数名放在同一行,如果放不下就对形参分行。
- 如果返回类型与函数声明或定义分行了, 不要缩进。
- 左圆括号总是和函数名在同一行。
- 函数名和左圆括号间永远没有空格。
- 圆括号与参数间没有空格。
- 左大括号总在最后一个参数同一行的末尾处, 不另起新行。
- 右大括号总是单独位于函数最后一行, 或者与左大括号同一行。
- 右圆括号和左大括号间总是有一个空格。
- 换行后的参数保持 4 个空格的缩进。
预处理指令
预处理指令不要缩进,从行首开始。
命名空间格式化
命名空间内容不缩进。