3246 字
16 分钟
[C++] 代码规范手册
TIP本文来自GoogleCpp代码规范
Waiting for api.github.com...如果对详细的内容有兴趣,可以点击此跳转下载最新的中文版Google代码规范
概述
对于高质量的工程,一般应满足以下要求:
- 代码简洁精炼、美观、可读性好、高效率、高复用、可移植性好、高内聚、低耦合,没有冗余。不符合这些原则,必须特别说明。
 - 规范性:代码有规可循。特殊排版、特殊语法、特殊指令,必须特别说明。
 
文件排版
2.1 包含头文件
- 系统头文件与用户头文件包含区分开,不交叉。
 - 系统头文件,稳定的目录结构,应采用包含子路径方式,如:
#include "sub/test.h"。 - 系统头文件应用:
#include <...>。 - 自定义同文件应用:
#include "xxx.h"。 - 只引用需要的头文件。
 - 将所有 
#include的文件名视为大小写敏感。能使用声明的情况下,使用声明。 
2.2 h 和 cpp 文件
- 头文件命名为 
*.h,内联文件命名为*.inl;C++ 文件命名为*.cpp。 - 文件名不要用无意义的名称,例如 
XImage.cpp,命名方式在系统中应该统一。 - 头文件除了特殊情况,应使用 
#ifdef控制块。 - 头文件 
#endif应采用行尾注释。 - 头文件结构:首先是包含全局函数,其次是宏定义代码块,然后是全局变量、全局常量、类型定义、类定义、内联部分。
 - cpp 文件结构:包含指令、宏定义、全局变量、函数定义。
 
2.3 文件结构
- 文件应包含文件头注释和内容。
 - 函数体、类体之间原则上用 1 个空行,特殊情况下可用一个或者不需要空行。
 
2.4 空行
- 文件头、控制块、
#include部分、宏定义部分、class部分、全局常量部分、全局变量部分、函数和函数之间,用一个空行。 
注释方面
3.1 文件头注释
- 包括作者、文件名称、文件说明、生成日期(可选),并提供模板。
 
3.2 函数注释
- 关键函数必须写上注释,说明函数的用途,并提供注释模板。
 - 特别函数参数,需要说明参数的目的,由谁负责释放等。
 - 除了特别情况,注释写在代码之前,不要放到代码行之后。
 - 对每个 
#else或#endif给出行末注释。 - 关键代码注释,包括但不限于:赋值、函数调用、表达式、分支等。
 - 尚未实现完整的代码,或者需要进一步优化的代码,应加上 
// TODO ...或同等含义的注释。 - 调试的代码,加上注释 
// DEBUG或同等含义的注释。 - 需要引起关注的代码,加上注释 
// NOTE ...或同等含义的注释。 - 对于较大的代码块结尾,如 
for、while、do等,可加上// end for|while|do或同等含义的注释。 
命名方面
4.1 原则
- 同一性:在编写一个子模块或派生类时,要遵循其基类或整体模块的命名风格,保持命名风格在整个模块中的同一性。
 - 标识符组成:标识符采用英文单词或其组合,应当直观且可以拼读,可望文知意,用词应当准确,避免用拼音命名。
 - 最小化长度 && 最大化信息量原则:在保持一个标识符意思明确的同时,应当尽量缩短其长度。
 - 避免过于相似:不要出现仅靠大小写区分的相似的标识符,例如 “i” 与 “I”,“function” 与 “Function” 等。
 - 避免在不同级别的作用域中重名:程序中不要出现名字完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但容易使人误解。
 - 正确命名具有互斥意义的标识符:用正确的反义词组命名具有互斥意义的标识符,如:“minValue” 和 “maxValue”,“getName()” 和 “setName()” 等。
 - 避免名字中出现数字编号:尽量避免名字中出现数字编号,如 Value1, Value2 等,除非逻辑上的确需要编号。
 
4.2 函数
- 类的函数名称应采用首字母小写类似 
handleXXX命名,例如:handleColor;不推荐采用例如HandleColor;除了标准 C 风格代码、标准模板库,不推荐用下划线,例如,handle_color,容易与系统函数标准函数重名,导致不能目视而知其作用范围。 - 函数参数比较多时,应考虑用结构代替,参数不能超过 6 个。
 - 如果不能避免函数参数比较多,应在排版上可考虑相似含义的参数占用一行,参数名竖向对齐,甚至每个参数一行。
 
4.3 变量
- 变量命名方式应采用驼峰命名方式,例如 
handleColor。不同类型变量打头方式见表 1,当类型叠加时,应以最能够表现变量类型的打头方式,例如 int 类型的指针,应采用p打头;指针类型的数组,应采用a打头,数组的指针因采用p打头。 
表 1 变量类型打头表
| 类型 | 打头 | 备注 | 
|---|---|---|
| int | n | 如 nValue | 
| bool | b | |
| long | l | 如 lValue | 
| float | f | |
| double | d | |
| std::string/QString | str | |
| std::list/QList | lst | 如 lstValues | 
| std::vector/QVector | vec | |
| std::map/QMap | map | |
| 枚举 | e | |
| 指针 | p | |
| 引用 | r | |
| 数组 | a | 
- 不同作用范围的变量应在类型前面添加前缀加下划线,不同作用范围的前缀见表 2。
 
表 2 变量作用范围前缀表
| 类型 | 前缀 | 备注 | 
|---|---|---|
| 成员变量 | m_ | 如 m_nValue | 
| 全局变量 | g_ | 如 g_lValue | 
| 静态变量 | s_ | 如 s_pValuePtr,静态全局和静态成员也应 s 为前缀 | 
4.4 类型名
- 类名和结构体首字母大写,例如 
MyClass,MyStruct。 - 类和对象名应是名词。
 - 枚举定义以 
E_打头,且应全大写,使用下划线分割不同的单词,如E_TYPE_STUDENT_INFO,各枚值名字应为枚举类型名打头,例如E_TYPE_STUDENT_INFO_NUMBER。 - 宏定义以 
DEF_打头,且应全大写,使用下划线分割不同的单词,如DEF_TYPE_ID。 
4.5 风格兼容性
- 对于移植的或者开源的代码,可以沿用原有风格,不用 C++ 的命名规范。
 
代码风格
5.1 Tab 和空格
- 每一行开始处的缩进只能用 4 个空格,不能用 Tab,输入内容之后统一用空格。
 - 在代码行的结尾部分不能出现多余的空格。
 - 不要在 
::、->、.前后加空格。 - 不要在 
,、;之前加空格。 
5.2 类型定义
- 类、结构、枚举、联合:大括号另起一行。
 - 函数体的 
{需要新起一行,在{之前的缩进应与上一级对齐。 - 除了特别情况,函数体内不能出现两个空行。
 - 除了特别情况,函数体内不能宏定义指令,除非该宏定义仅能在该函数内使用。
 - 在一个函数体内,逻辑上密切相关的语句之间不加空行,其他地方应加空行分隔。
 
5.3 代码块
"if"、"for"、"while"、"do"、"try"、"catch"等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写和修改代码时出现失误。"if"、"for"、"while"、"do"、"try"、"catch"的括号和表达式,括号可紧挨关键字,这样强调的是表达式。
5.4 代码行
- 一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。
 - 多行变量定义,为了追求代码排版美观,可将变量竖向对齐。
 - 代码行最大长度宜控制在一定个字符以内,能在当前屏幕内全部可见为宜。建议 80 个。
 
5.5 宏
- 不要用分号结束宏定义。
 - 能使用函数来解决的问题,不要使用宏,方便调试。
 
5.6 goto
- 不要用 
goto。 
类型
- 定义指针和引用时 
*和&紧跟变量名。 - 尽量避免使用浮点数,除非必须。
 - 用 
typedef简化程序中的复杂语法。 - 避免定义无名称的类型。例如:
enum TState { EIdle, EActive }; - 少用 
union,如果一定要用,则采用简单数据类型成员。 - 用 
enum取代(一组相关的)常量。 - 不要使用魔鬼数字。
 - 尽量用引用取代指针。
 - 定义变量完成后立即初始化,勿等到使用时才进行,在需要使用到变量的时候才定义变量。
 - 如果有更优雅的解决方案,不要使用强制类型转换。例如 
dynamic_cast,尽量使用多态。 
表达式
- 避免在表达式中用赋值语句。
 - 避免对浮点类型做等于或不等于判断。
 - 不能将枚举类型进行运算后再赋给枚举变量。
 - 在循环过程中不要修改循环计数器。
 - 检测空指针,用 
if (p) - 检测非空指针,用 
if (!p) 
函数
8.1 引用
- 引用类型作为返回值:函数必须返回一个存在的对象。
 - 引用类型作为参数:调用者必须传递一个存在的对象。
 
8.2 返回值
- 除开 
void函数、构造函数、析构函数,其他函数必须要有返回值。在函数的实现中,每个分支必须显示返回return。 - 当函数返回引用或指针时,用文字描述其有效期。
 
8.3 内联函数
- 内联函数应将函数体放到类体外。
 - 只有简单的函数才有必要设计为内联函数,复杂业务逻辑的函数不要这么做。
 - 虚函数不要设计为内联函数。
 
8.4 函数参数
- 只读取该参数的内容,不对其内容做修改,用常量引用。
 - 修改参数内容,或需要通过参数返回,用非常量引用。
 - 简单数据类型用传值方式。
 - 复杂数据类型用引用或指针方式。
 - 输入参数排在前面,输出参数排在后面,默认参数除外。
 - 除通用库函数,尽量不使用默认参数。
 
类
9.1 整体结构
- 按照 
public、protected、private的顺序分块。哪一块没有,就直接忽略。 - 每一块中,按照以下顺序排列:
typedef,enum,struct,class定义的嵌套类型- 常量
 - 构造函数
 - 析构函数
 - 成员函数(含静态成员函数)
 - 数据成员(含静态数据成员)
 
 .cpp文件中,函数的实现尽可能和声明次序一致。
9.2 构造函数
- 构造函数的初始化列表,应和类里成员变量的顺序一致。
 - 初始化列表中的每个项,应独占一行。
 - 避免出现用一个成员初始化另一个成员。
 - 构造函数应初始化所有成员,尤其是指针。
 - 不要在构造函数和析构函数中抛出异常。
 
9.3 纯虚函数
- 接口类的虚函数应设计为纯虚函数。
 
9.4 构造和析构函数
- 如果类可以继承,则应将类析构函数设计为虚函数。
 - 如果类不允许继承,则应将类析构函数设计为非虚函数。
 - 如果类不能被复制,则应将拷贝构造函数和赋值运算符设计为私有的。
 - 如果为类设计了构造函数,则应有析构函数。
 
9.5 成员变量
- 尽量避免使用 
mutable(mutex除外)和volatile。 - 尽量避免使用公有成员变量。
 
9.6 成员函数
- 努力使类的接口少而完备。
 - 尽量使用常成员函数代替非常量成员函数,
const函数。 - 除非特别理由,绝不要重新定义非虚函数。
 - 如果是子类型重写父类的虚函数,应该在函数声明后面添加 
override,让编译器来检查是否重新定义非虚函数(C++11)。 - 不想被子类重写的虚函数,函数声明后面添加 
final(C++11)。 
9.7 继承
- 继承必须满足 IS-A 的关系,HAS-A 应采用包含。
 - 虚函数不要采用默认参数。
 - 除非特别需要,应避免设计大而全的虚函数,虚函数功能要单一。
 - 除非特别需要,避免将基类强制转换成派生类。
 
错误处理
- 释放内存完成后将指针赋空,避免出现野指针。
 - 使用指针前进行判断合法性,应考虑到为空的情况的处理,除非明显的组合模式。
 - 使用数组时,应先判断索引的有效性,处理无效的索引的情况。
 - 卫句风格:先处理所有可能发生错误的情况,再处理正常情况。
 
性能
- 头文件中使用前向声明代替头文件包含。例如:
class M; - 尽量在 
for循环之前,先写计算估值表达式。 - 尽量避免在循环体内部定义对象。
 - 避免对象拷贝,尤其是代价很高的对象拷贝。
 - 避免生成临时对象,尤其是大的临时对象。
 - 注意大尺寸对象数组。
 - 尽量使用标准库中封装的算法。
 
