背景
C++是很多项目的主要开发语言。项目一般是多人开发,而每个人的编码风格一般各有不同。如果没有一套C++的编程规范,项目内的代码将难以阅读与维护。
因此此文档将对C++的编程风格作一些约定,以便项目内的开发者可以写出风格一致的代码。
下面将围绕以下思维导图展开讨论C++编程规范的细节
头文件
自包含头文件
头文件应该能够自给自足,例如头文件 foo.h 可以置于 foo.cpp 文件的第一行
换句话说,头文件包含了它所需要的其他头文件,当别的文件引用此头文件的时候,不需要额外引用其他头文件才能够使用。
#define保护
头文件应该使用#define保护,防止头文件被多重包含。
按以下方式保护:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
内联函数
只有当函数在10行以内才将其定义为内联函数。
内联函数一般不能包含循环、switch语句、递归或者是虚函数。
#include的路径及顺序
例如dir/foo.cpp包含的头文件的次序如下:
- dir/foo.h
- C 系统文件
- C++ 系统文件
- 其他库的 .h 文件
- 本项目内 .h 文件
作用域
命名空间
鼓励在cpp文件中使用具名命名空间
-
不应该使用using引入整个命名空间
//禁止把整个命名空间引入 -- 会污染命名空间 using namespace foo;
-
不应该在头文件使用命名空间别名
//禁止使用别名命名空间在头文件中 -- 会导致别名命名空间成为公开API的一部分 namespace baz = ::foo::bar::baz;
-
禁止使用内联命名空间
//禁止使用内联 -- 会带来API迷惑 inline namespace FOO { }
局部变量
将函数变量尽量置于最小作用域内,并对变量声明时进行初始化。
类
构造函数
- 不要在构造函数中调用虚函数
- 不要在构造函数中进行可能失败的初始化
默认构造函数
如果一个类定义了若干个成员变量又没有其他构造函数,必须定义一个默认构造函数。否则编译器会自动产生一个很糟糕的默认构造函数。
显式构造函数
对于单个参数的构造函数,使用关键字explicit.
拷贝构造函数
仅在一个类需要拷贝一个类的对象时才使用拷贝构造函数。大部分情况下不需要拷贝的类,应该使用DISALLOW_COPY_AND_ASSIGN.
DISALLOW_COPY_AND_ASSIGN的使用方法如下:
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
//把拷贝构造和赋值操作声明为private
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
结构体 VS. 类
仅当数据时使用struct。否则使用class
继承
使用组合常常比使用继承更合理。
如果使用继承,一般定义为public继承
多重继承
真正需要多重继承的情况少之又少。
只有一种情况允许使用多重继承:最多只有一个基类时非抽象类,其他基类都是抽象类
运算符重载
只有在少数特定情况下使用。
否则尽量不要使用运算符重载
成员变量
将所有数据成员变量声明为private.。
除非是 static const 类型成员
声明顺序
一般应该以public开始,后跟protected,最有是private
将类似的声明放在一起
函数
参数顺序
输入参数在先,后跟输出参数
编写简短函数
函数一般要求不超过40行
引用输入参数
输入型的引用参数,必须加上const
例如
void foo(const string &in, string& out);
缺省参数
建议使用缺省参数,但不允许在虚函数中使用
语言特性
变长数组和alloca()
不允许使用变长数组和alloca()
异常
不使用异常
运行时类型识别
禁止使用RTTI
RTTI允许程序员通过typeid或者dynamic_cast在运行时识别C++对象的类型
前置自增和自减
对于自增(++i 或 i++),如果自增后,返回值没有被用到,前置自增(++i)要比后置自增(i++)效率更高
const用法
强烈建议在任何可能的情况下使用const
预处理宏
使用宏时需要非常小心,尽量用内联函数、枚举和常量代替
0,nullptr和NULL
整数用0,实数用0.0,指针用nullptr或者NULL,字符用'\0'
sizeof
尽量用sizeof(varname)代替sizeof(type)
模板编程
不要使用复杂的模板编程
命名约定
通用命名规则
- 函数命名,变量命名,文件命名应该具备描述性,不要过度缩写
- 类型和变量应该是名词
- 函数应该是动词
好的命名:
int num_errors; // Good.
int num_completed_connections; // Good.
int price_count_reader; // 无缩写
int num_errors; // "num" 是一个常见的写法
int num_dns_connections; // 人人都知道 "DNS" 是什么
不好的命名:
int n; // 毫无意义.
int nerr; // 含糊不清的缩写.
int n_comp_conns; // 含糊不清的缩写.
int wgc_connections; // 只有贵团队知道是什么意思.
int pc_reader; // "pc" 有太多可能的解释了.
int cstmr_id; // 删减了若干字母.
文件命名
文件名要全部小写,可以包含下划线 (_) 或者连字符 (-)
例如:
my_userful_class.cpp
类命名
类名字的每个单词首字母大写,不包含下划线
例如:MyExcitingClass
变量命名
变量和数据成员名使用小写,单词间使用下划线连接,类成员变量以下划线结尾
例如变量:
string table_name;
例如类数据成员
class Foo {
...
private:
string table_name_;
};
常量命名
命名以"k"开头,大小写混合,例如:
const int kDaysInAWeek = 7;
函数命名
常规函数使用大小写混合,例如
void OnMessage();
取值和设值函数,使用变量名字来命名,例如:
class Foo {
public:
int num_entries() const { return num_entries_;}
private:
int num_entries_;
};
命名空间命名
以大小写字母命名
枚举命名
使用常量或宏的命名方法命名
例如:
enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
宏命名
使用大写字母和下划线来命名
注释
类注释
- 描述类用法的注释应该放在头文件
- 描述类的实现的注释应该放在实现文件中
函数注释
函数声明的注释内容:
- 函数的输入输出
- 函数调用期间,是否会释放某些参数
- 如果函数分配了空间,需要由调用者释放
- 参数是否可以为空指针
- 是否存在性能隐患
- 函数是否可重入
变量注释
通常根据变量名就可以说明变量用途
实现注释
对于代码中晦涩,重要的地方加以注释
格式
行长度
每行代码不超过80个字符
空格还是制表位
只是用空格,每次缩进2个空格
函数声明和定义
返回类型和函数名在同一行,参数也尽量在同一样,否则参数分行放置
引用自(谷歌C++编程规范)