https://zh-google-styleguide.readthedocs.io/en/latest/contents/#
这个Google C++风格指南
摘要如下;
+++++++++++++++++++++++++++++++++++++++++
3.4. 结构体 VS. 类
仅当只有数据成员时使用 struct, 其它一概使用 class.
3.5. 继承
所有继承必须是 public 的.
对于可能被子类访问的成员函数, 不要过度使用 protected 关键字. 注意, 数据成员都必须是 私有的.
3.8. 运算符重载
只有在意义明显, 不会出现奇怪的行为并且与对应的内建运算符的行为一致时才定义重载运算符.
3.9. 存取控制
将 所有 数据成员声明为 private, 除非是 static const 类型成员
4.3. 引用参数
函数参数列表中, 所有引用参数都必须是 const:
void Foo(const string &in, string *out);
6.3. 函数重载
如果您打算重载一个函数, 可以试试改在函数名里加上参数信息。例如,用 AppendString() 和 AppendInt() 等, 而不是一口气重载多个 Append().
6.4. 缺省参数
我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。
6.12. const 用法
const 变量, 数据成员, 函数和参数为编译时类型检测增加了一层保障; 便于尽早发现错误. 因此, 我们强烈建议在任何可能的情况下使用 const:
如果函数不会修改传你入的引用或指针类型参数, 该参数应声明为 const.
尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.
如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.
const int* foo, 将 const 放在前面才更易读, 因为在自然语言中形容词 (const) 是在名词 (int) 之前.
6.18. sizeof
尽可能用 sizeof(varname) 代替 sizeof(type).
使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新.
6.22. 模板编程
模板编程有时候能够实现更简洁更易用的接口, 但是更多的时候却适得其反. 因此模板编程最好只用在少量的基础组件, 基础数据结构上, 因为模板带来的额外的维护成本会被大量的使用给分担掉
如果你使用模板编程, 你必须考虑尽可能的把复杂度最小化, 并且尽量不要让模板对外暴漏. 你最好只在实现里面使用模板, 然后给用户暴露的接口里面并不使用模板, 这样能提高你的接口的可读性. 并且你应该在这些使用模板的代码上写尽可能详细的注释.
++++++++++++++++++++++++++++++++++++++++++++++++++++++
7.1. 通用命名规则
尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词.
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; // 删减了若干字母.
注意, 一些特定的广为人知的缩写是允许的, 例如用 i 表示迭代变量和用 T 表示模板参数.
模板参数的命名应当遵循对应的分类: 类型模板参数应当遵循 类型命名 的规则, 而非类型模板应当遵循 变量命名 的规则.
7.2. 文件命名
文件名要全部小写, 可以包含下划线 ( _ ) 或连字符 ( - ), 依照项目的约定. 如果没有约定, 那么 “_” 更好.
7.3. 类型命名
类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.
所有类型命名 —— 类, 结构体, 类型定义 (typedef), 枚举, 类型模板参数 —— 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线. 例如:
// 类和结构体
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// 类型定义
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// using 别名
using PropertiesMap = hash_map<UrlTableProperties *, string>;
// 枚举
enum UrlTableErrors { ...
7.4. 变量命名
变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
普通变量命名
举例:
string table_name; // 好 - 用下划线.
string tablename; // 好 - 全小写.
string tableName; // 差 - 混合大小写
类数据成员
不管是静态的还是非静态的, 类数据成员都可以和普通变量一样, 但要接下划线.
class TableInfo {
...
private:
string table_name_; // 好 - 后加下划线.
string tablename_; // 好.
static Pool<TableInfo>* pool_; // 好.
};
结构体变量
不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 不用像类那样接下划线:
struct UrlTableProperties {
string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};
7.5. 常量命名
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:
const int kDaysInAWeek = 7;
所有具有静态存储类型的变量 (例如静态变量或全局变量) 都应当以此方式命名.
7.6. 函数命名
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配:
MyExcitingFunction(), MyExcitingMethod(),
或者
my_exciting_member_variable(), set_my_exciting_member_variable().
一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc() 而非 StartRPC()).
同样的命名规则同时适用于类作用域与命名空间作用域的常量, 因为它们是作为 API 的一部分暴露对外的, 因此应当让它们看起来像是一个函数,
取值和设值函数的命名与变量一致. 一般来说它们的名称与实际的成员变量对应.
例如 int count() 与 void set_count(int count).
7.7. 命名空间命名
命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称.
7.8. 枚举命名
枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
单独的枚举值应该优先采用 常量 的命名方式. 但 宏 方式的命名也可以接受. 枚举名 UrlTableErrors (以及 AlternateUrlTableErrors) 是类型, 所以要用大小写混合的方式.
enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
优先选择常量风格的命名方式. 新代码应该尽可能优先使用常量风格. 但是老代码没必要切换到常量风格,
7.9. 宏命名
像这样命名:
MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
全部大写, 使用下划线:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9.1. 行长度
每一行代码字符数不超过 80.
如果无法在不伤害易读性的条件下进行断行, 那么注释行可以超过 80 个字符, 这样可以方便复制粘贴. 例如, 带有命令示例或 URL 的行可以超过 80 个字符.
包含长路径的 #include 语句可以超出80列.
头文件保护 可以无视该原则.
9.2. 非 ASCII 字符
尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.
9.3. 空格还是制表位
只使用空格, 每次缩进 4个空格.
我们使用空格缩进. 不要在代码中使用制表符. 你应该设置编辑器将制表符转为空格.
9.4. 函数声明与定义
返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行, 分行方式与 函数调用 一致.
函数看上去像这样:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
如果同一行文本太多, 放不下所有参数:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3) {
DoSomething();
...
}
甚至连第一个参数都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
...
}
注意以下几点:
使用好的参数名.
只有在参数未被使用或者其用途非常明显时, 才能省略参数名.
如果返回类型和函数名在一行放不下, 分行.
如果返回类型与函数声明或定义分行了, 不要缩进.
左圆括号总是和函数名在同一行.
函数名和左圆括号间永远没有空格.
圆括号与参数间没有空格.
左大括号总在最后一个参数同一行的末尾处, 不另起新行.
右大括号总是单独位于函数最后一行, 或者与左大括号同一行.
右圆括号和左大括号间总是有一个空格.
所有形参应尽可能对齐.
缺省缩进为 2 个空格.
换行后的参数保持 4 个空格的缩进.
未被使用的参数, 或者根据上下文很容易看出其用途的参数, 可以省略参数名:
9.6. 函数调用
要么一行写完函数调用, 要么在圆括号里对参数分行, 要么参数另起一行且缩进4格. 如果没有其它顾虑的话, 尽可能精简行数, 比如把多个参数适当地放在同一行里.
函数调用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
如果一些参数本身就是略复杂的表达式, 且降低了可读性, 那么可以直接创建临时变量描述该表达式, 并传递给函数:
int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);
9.7. 列表初始化格式
如果列表初始化伴随着名字, 比如类型或变量名, 格式化时将将名字视作函数调用名, {} 视作函数调用的括号. 如果没有名字, 就视作名字长度为零.
// 一行列表初始化示范.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar};
// 当不得不断行时.
SomeFunction(
{"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.
some_other_function_parameter);
SomeType variable{
some, other, values,
{"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.
SomeOtherType{
"Very long string requiring the surrounding breaks.", // 非常长的字符串, 前后都需要断行.
some, other values},
SomeOtherType{"Slightly shorter string", // 稍短的字符串.
some, other, values}};
SomeType variable{
"This is too long to fit all in one line"}; // 字符串过长, 因此无法放在同一行.
MyType m = { // 注意了, 您可以在 { 前断行.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};
9.8. 条件语句
倾向于不在圆括号内使用空格. 关键字 if 和 else 另起一行.
if (condition) { // 圆括号里没有空格.
... // 2 空格缩进.
} else if (...) { // else 与 if 的右括号同一行.
...
} else {
...
}
注意所有情况下 if 和左圆括号间都有个空格. 右圆括号和左大括号之间也要有个空格:
if(condition) // 差 - IF 后面没空格.
if (condition){ // 差 - { 前面没空格.
if(condition){ // 变本加厉地差.
if (condition) { // 好 - IF 和 { 都与空格紧邻.
复杂的条件或循环语句用大括号可读性会更好. 也有一些项目要求 if 必须总是使用大括号:
if (condition) {
DoSomething(); // 2 空格缩进.
}
但如果语句中某个 if-else 分支使用了大括号的话, 其它分支也必须使用:
// 只要其中一个分支用了大括号, 两个分支都要用上大括号.
if (condition) {
foo;
} else {
bar;
}
9.9. 循环和开关选择语句
switch 语句可以使用大括号分段, 以表明 cases 之间不是连在一起的. 在单语句循环里, 括号可用可不用. 空循环体应使用 {} 或 continue.
如果有不满足 case 条件的枚举值, switch 应该总是包含一个 default 匹配 (如果有输入值没有 case 去处理, 编译器将给出 warning).
switch (var) {
case 0: { // 2 空格缩进
... // 4 空格缩进
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
在单语句循环里, 括号建议使用:
for (int i = 0; i < kSomeNumber; ++i) {
printf("I take it back\n");
}
空循环体应使用 {} 或 continue, 而不是一个简单的分号.
while (condition) {
} // 反复循环直到条件失效.
for (int i = 0; i < kSomeNumber; ++i) {
} // 可 - 空循环体.
while (condition)
continue; // 可 - contunue 表明没有逻辑.
9.10. 指针和引用表达式
句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格.
下面是指针和引用表达式的正确使用范例:
x = *p;
p = &x;
x = r.y;
x = r->y;
注意:
在访问成员时, 句点或箭头前后没有空格.
指针操作符 * 或 & 后没有空格.
在声明指针变量或参数时, 星号与类型或变量名紧挨都可以:
// 好, 空格前置.
char *c;
const string &str;
// 好, 空格后置.
char* c;
const string& str;
char * c; // 差 - * 两边都有空格
const string & str; // 差 - & 两边都有空格.
int x, *y; // 不允许 - 在多重声明中不能使用 & 或 *
9.11. 布尔表达式
如果一个布尔表达式超过 标准行宽, 断行方式要统一一下.
下例中, 逻辑与 (&&) 操作符总位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another &&
last_one) {
...
}
if (this_one_thing > this_other_thing
&& a_third_thing == a_fourth_thing
&&yet_another
&& last_one) {
...
}
if ( (this_one_thing > this_other_thing) &&
(a_third_thing == a_fourth_thing) &&
yet_another &&
last_one) {
...
}
上例的逻辑与 (&&) 操作符均位于行尾. 把所有操作符放在开头也可以.
可以考虑额外插入圆括号, 合理使用的话对增强可读性是很有帮助的.
9.12. 函数返回值
不要在 return 表达式里加上非必须的圆括号.
只有在写 x = expr 要加上括号的时候才在 return expr; 里使用括号.
return result; // 返回值很简单, 没有圆括号.
// 可以用圆括号把复杂表达式圈起来, 改善可读性.
return (some_long_condition &&
another_condition);
9.13. 变量及数组初始化
您可以用 =, () 和 {}, 以下的例子都是正确的:
int x = 3;
int x(3);
int x{3};
string name("Some Name");
string name = "Some Name";
string name{"Some Name"};
9.14. 预处理指令
预处理指令不要缩进, 从行首开始.
即使预处理指令位于缩进代码块中, 指令也应从行首开始.
// 好 - 指令从行首开始
if (lopsided_score) {
#if DISASTER_PENDING // 正确 - 从行首开始
DropEverything();
# if NOTIFY // 非必要 - # 后跟空格
NotifyClient();
# endif
#endif
BackToNormal();
}
9.15. 类格式
访问控制块的声明依次序是 public:, protected:, private:, 每个都缩进2 个空格.
9.16. 构造函数初始值列表
构造函数初始化列表放在同一行或按四格缩进并排多行.
下面两种初始值列表方式都可以接受:
// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// 如果不能放在同一行,
// 必须置于冒号后, 并缩进 4 个空格
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// 如果初始化列表需要置于多行, 将每一个成员放在单独的一行
// 并逐行对齐
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
}
// 右大括号 } 可以和左大括号 { 放在同一行
// 如果这样做合适的话
MyClass::MyClass(int var)
: some_var_(var) {}
9.17. 命名空间格式化
命名空间内容不缩进.
命名空间 不要增加额外的缩进层次, 例如:
namespace {
void foo() { // 正确. 命名空间内没有额外的缩进.
...
}
} // namespace
声明嵌套命名空间时, 每个命名空间都独立成行.
namespace foo {
namespace bar {