格式
尽管有些编程的排版风格因人而异,但是我们强烈建议和要求使用统一的编码风格,以便所有人都能够轻松的阅读和理解代码,增强代码的可维护性。
行宽
建议3.1.1 行宽不超过 120 个字符
建议每行字符数不要超过 120 个。如果超过120个字符,请选择合理的方式进行换行。
例外:
- 如果一行注释包含了超过120 个字符的命令或URL,则可以保持一行,以方便复制、粘贴和通过grep查找;
- 包含长路径的 #include 语句可以超出120 个字符,但是也需要尽量避免;
- 编译预处理中的error信息可以超出一行。预处理的 error 信息在一行便于阅读和理解,即使超过 120 个字符。
#ifndef XXX_YYY_ZZZ
#error Header aaaa/bbbb/cccc/abc.h must only be included after xxxx/yyyy/zzzz/xyz.h, because xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#endif
缩进
规则3.2.1 使用空格进行缩进,每次缩进2个空格
只允许使用空格(space)进行缩进,每次缩进为 2 个空格。
大括号
规则3.3.1 除函数外,使用 K&R 缩进风格
K&R风格函数左大括号跟随语句放行末。右大括号独占一行,除非后面跟着同一语句的剩余部分,如 do 语句中的 while,或者 if 语句的 else/else if,或者逗号、分号。
如:
struct MyType { // 跟随语句放行末,前置1空格
...
};
int Foo(int a) { // 函数左大括号跟随语句放行末
if (...) {
...
} else {
...
}
}
推荐这种风格的理由:
- 代码更紧凑;
- 相比另起一行,放行末使代码阅读节奏感上更连续;
- 符合后来语言的习惯,符合业界主流习惯;
- 现代集成开发环境(IDE)都具有代码缩进对齐显示的辅助功能,大括号放在行尾并不会对缩进和范围产生理解上的影响。对于空函数体,可以将大括号放在同一行:
class MyClass {
public:
MyClass() : value(0) {}
private:
int value;
};
函数声明和定义
规则3.4.1 函数声明和定义的返回类型和函数名在同一行;函数参数列表超出行宽时要换行并合理对齐
在声明和定义函数的时候,函数的返回值类型应该和函数名在同一行;如果行宽度允许,函数参数也应该放在一行;否则,函数参数应该换行,并进行合理对齐。参数列表的左圆括号总是和函数名在同一行,不要单独一行;右圆括号总是跟随最后一个参数。
换行举例:
ReturnType FunctionName(ArgType paramName1, ArgType paramName2) { // Good:全在同一行
...
}
ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1, // 行宽不满足所有参数,进行换行
ArgType paramName2, // Good:和上一行参数对齐
ArgType paramName3) {
...
}
ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // 行宽限制,进行换行
ArgType paramName3, ArgType paramName4, ArgType paramName5) { // Good: 换行后 4 空格缩进
...
}
ReturnType ReallyReallyReallyReallyLongFunctionName( // 行宽不满足第1个参数,直接换行
ArgType paramName1, ArgType paramName2, ArgType paramName3) { // Good: 换行后 4 空格缩进
...
}
函数调用
规则3.5.1 函数调用入参列表应放在一行,超出行宽换行时,保持参数进行合理对齐
函数调用时,函数参数列表放在一行。参数列表如果超过行宽,需要换行并进行合理的参数对齐。左圆括号总是跟函数名,右圆括号总是跟最后一个参数。
换行举例:
ReturnType result = FunctionName(paramName1, paramName2); // Good:函数参数放在一行
ReturnType result = FunctionName(paramName1,
paramName2, // Good:保持与上方参数对齐
paramName3);
ReturnType result = FunctionName(paramName1, paramName2,
paramName3, paramName4, paramName5); // Good:参数换行,4 空格缩进
ReturnType result = VeryVeryVeryLongFunctionName( // 行宽不满足第1个参数,直接换行
paramName1, paramName2, paramName3); // 换行后,4 空格缩进
如果函数调用的参数存在内在关联性,按照可理解性优先于格式排版要求,对参数进行合理分组换行。
// Good:每行的参数代表一组相关性较强的数据结构,放在一行便于理解
int result = DealWithStructureLikeParams(left.x, left.y, // 表示一组相关参数
right.x, right.y); // 表示另外一组相关参数
if语句
规则3.6.1 if语句必须要使用大括号
我们要求if语句都需要使用大括号,即便只有一条语句。
理由:
- 代码逻辑直观,易读;
- 在已有条件语句代码上增加新代码时不容易出错;
- 对于在if语句中使用函数式宏时,有大括号保护不易出错(如果宏定义时遗漏了大括号)。
if (objectIsNotExist) { // Good:单行条件语句也加大括号
return CreateNewObject();
}
规则3.6.2 禁止 if/else/else if 写在同一行
条件语句中,若有多个分支,应该写在不同行。
如下是正确的写法:
if (someConditions) {
DoSomething();
...
} else { // Good: else 与 if 在不同行
...
}
下面是不符合规范的案例:
if (someConditions) { ... } else { ... } // Bad: else 与 if 在同一行
循环语句
规则3.7.1 循环语句要求使用大括号
和if语句类似,我们要求for/while循环语句必须加上的大括号,即使循环体是空的,或者循环语句只有一条。
for (int i = 0; i < someRange; i++) {
DoSomething();
}
如果循环体是空的,应该使用空的大括号,而不是使用单个分号。 单个分号容易被遗漏,也容易被误认为是循环语句中的一部分。
for (int i = 0; i < someRange; i++) { } // Good: for循环体是空,使用大括号,而不是使用分号
while (someCondition) { } // Good:while循环体是空,使用大括号,而不是使用分号
while (someCondition) {
continue; // Good:continue表示空逻辑,可以使用大括号也可以不使用
}
坏的例子:
for (int i = 0; i < someRange; i++) ; // Bad: for循环体是空,也不要只使用分号,要使用大括号
while (someCondition) ; // Bad:使用分号容易让人误解是while语句中的一部分
switch语句
规则3.8.1 switch 语句的 case/default 要缩进一层
switch 语句的缩进风格如下:
switch (var) {
case 0: // Good: 缩进
DoSomething1(); // Good: 缩进
break;
case 1: { // Good: 带大括号格式
DoSomething2();
break;
}
default:
break;
}
switch (var) {
case 0: // Bad: case 未缩进
DoSomething();
break;
default: // Bad: default 未缩进
break;
}
表达式
建议3.9.1 表达式换行要保持换行的一致性,运算符放行末
较长的表达式,不满足行宽要求的时候,需要在适当的地方换行。一般在较低优先级运算符或连接符后面截断,运算符或连接符放在行末。运算符、连接符放在行末,表示“未结束,后续还有”。例:
// 假设下面第一行已经不满足行宽要求
if (currentValue > threshold && // Good:换行后,逻辑操作符放在行尾
someConditionsion) {
DoSomething();
...
}
int result = reallyReallyLongVariableName1 + // Good
reallyReallyLongVariableName2;
表达式换行后,注意保持合理对齐,或者4空格缩进。参考下面例子
int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
longVaribleName4 + longVaribleName5 + longVaribleName6; // Good: 4空格缩进
int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
longVaribleName4 + longVaribleName5 + longVaribleName6; // Good: 保持对齐
变量赋值
规则3.10.1 多个变量定义和赋值语句不允许写在一行
每行只有一个变量初始化的语句,更容易阅读和理解。
int maxCount = 10;
bool isCompleted = false;
下面是不符合规范的示例:
int maxCount = 10; bool isCompleted = false; // Bad:多个变量初始化需要分开放在多行,每行一个变量初始化
int x, y = 0; // Bad:多个变量定义需要分行,每行一个
int pointX;
int pointY;
...
pointX = 1; pointY = 2; // Bad:多个变量赋值语句放同一行
例外:for 循环头、if 初始化语句(C++17)、结构化绑定语句(C++17)中可以声明和初始化多个变量。这些语句中的多个变量声明有较强关联,如果强行分成多行会带来作用域不一致,声明和初始化割裂等问题。
初始化
初始化包括结构体、联合体、及数组的初始化
规则3.11.1 初始化换行时要有缩进,并进行合理对齐
结构体或数组初始化时,如果换行应保持4空格缩进。从可读性角度出发,选择换行点和对齐位置。
const int rank[] = {
16, 16, 16, 16, 32, 32, 32, 32,
64, 64, 64, 64, 32, 32, 32, 32
};
指针与引用
建议3.12.1 指针类型"*"跟随变量名或者类型,不要两边都留有或者都没有空格
指针命名: *
靠左靠右都可以,但是不要两边都有或者都没有空格。
int* p = NULL; // Good
int *p = NULL; // Good
int*p = NULL; // Bad
int * p = NULL; // Bad
例外:当变量被 const 修饰时,"*
" 无法跟随变量,此时也不要跟随类型。
char * const VERSION = "V100";
建议3.12.2 引用类型"&"跟随变量名或者类型,不要两边都留有或者都没有空格
引用命名:&
靠左靠右都可以,但是不要两边都有或者都没有空格。
int i = 8;
int& p = i; // Good
int &p = i; // Good
int & p = i; // Bad
int&p = i; // Bad
编译预处理
规则3.13.1 编译预处理的"#"统一放在行首,嵌套编译预处理语句时,"#"不缩进
编译预处理的"#"统一放在行首,即使编译预处理的代码是嵌入在函数体中的,"#"也应该放在行首。
#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) // Good:"#"放在行首
#define ATOMIC_X86_HAS_CMPXCHG16B 1 // Good:"#"放在行首
#else
#define ATOMIC_X86_HAS_CMPXCHG16B 0
#endif
int FunctionName() {
if (someThingError) {
...
#ifdef HAS_SYSLOG // Good:即便在函数内部,"#"也放在行首
WriteToSysLog();
#else
WriteToFileLog();
#endif
}
}
内嵌的预处理语句"#"不缩进
#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
#define ATOMIC_X86_HAS_CMPXCHG16B 1 // Good:区分层次,便于阅读
#else
#define ATOMIC_X86_HAS_CMPXCHG16B 0
#endif
空格和空行
建议3.14.1 水平空格应该突出关键字和重要信息,避免不必要的留白
水平空格应该突出关键字和重要信息,每行代码尾部不要加空格。总体规则如下:
- if, switch, case, do, while, for等关键字之后加空格;
- 小括号内部的两侧,不要加空格;
- 大括号内部两侧有无空格,左右必须保持一致;
- 一元操作符(& * + ‐ ~ !)之后不要加空格;
- 二元操作符(= + ‐ < > * / % | & ^ <= >= == != )左右两侧加空格
- 三目运算符(? :)符号两侧均需要空格
- 前置和后置的自增、自减(++ —)和变量之间不加空格
- 结构体成员操作符(. ->)前后不加空格
- 逗号(,)前面不加空格,后面增加空格
- 对于模板和类型转换(<>)和类型之间不要添加空格
- 域操作符(::)前后不要添加空格
- 冒号(:)前后根据情况来判断是否要添加空格常规情况:
void Foo(int b) { // Good:大括号前应该留空格
int i = 0; // Good:变量初始化时,=前后应该有空格,分号前面不要留空格
int buf[kBufSize] = {0}; // Good:大括号内两侧都无空格
函数定义和函数调用:
int result = Foo(arg1,arg2);
^ // Bad: 逗号后面需要增加空格
int result = Foo( arg1, arg2 );
^ ^ // Bad: 函数参数列表的左括号后面不应该有空格,右括号前面不应该有空格
指针和取地址
x = *p; // Good:*操作符和指针p之间不加空格
p = &x; // Good:&操作符和变量x之间不加空格
x = r.y; // Good:通过.访问成员变量时不加空格
x = r->y; // Good:通过->访问成员变量时不加空格
操作符:
x = 0; // Good:赋值操作的=前后都要加空格
x = -5; // Good:负数的符号和数值之前不要加空格
++x; // Good:前置和后置的++/--和变量之间不要加空格
x--;
if (x && !y) // Good:布尔操作符前后要加上空格,!操作和变量之间不要空格
v = w * x + y / z; // Good:二元操作符前后要加空格
v = w * (x + z); // Good:括号内的表达式前后不需要加空格
int a = (x < y) ? x : y; // Good: 三目运算符, ?和:前后需要添加空格
循环和条件语句:
if (condition) { // Good:if关键字和括号之间加空格,括号内条件语句前后不加空格
...
} else { // Good:else关键字和大括号之间加空格
...
}
while (condition) {} // Good:while关键字和括号之间加空格,括号内条件语句前后不加空格
for (int i = 0; i < someRange; ++i) { // Good:for关键字和括号之间加空格,分号之后加空格
...
}
switch (condition) { // Good: switch 关键字后面有1空格
case 0: // Good:case语句条件和冒号之间不加空格
...
break;
...
default:
...
break;
}
模板和转换
// 尖括号(< and >) 不与空格紧邻, < 前没有空格, > 和 ( 之间也没有.
vector<string> x;
y = static_cast<char*>(x);
// 在类型与指针操作符之间留空格也可以, 但要保持一致.
vector<char *> x;
域操作符
std::cout; // Good: 命名空间访问,不要留空格
int MyClass::GetValue() const {} // Good: 对于成员函数定义,不要留空格
冒号
// 添加空格的场景
// Good: 类的派生需要留有空格
class Sub : public Base {
};
// 构造函数初始化列表需要留有空格
MyClass::MyClass(int var) : someVar(var) {
DoSomething();
}
// 位域表示也留有空格
struct XX {
char a : 4;
char b : 5;
char c : 4;
};
// 不添加空格的场景
// Good: 对于public:, private:这种类访问权限的冒号不用添加空格
class MyClass {
public:
MyClass(int var);
private:
int someVar;
};
// 对于switch-case的case和default后面的冒号不用添加空格
switch (value) {
case 1:
DoSomething();
break;
default:
break;
}
注意:当前的集成开发环境(IDE)可以设置删除行尾的空格,请正确配置。
建议3.14.2 合理安排空行,保持代码紧凑
减少不必要的空行,可以显示更多的代码,方便代码阅读。下面有一些建议遵守的规则:
- 根据上下内容的相关程度,合理安排空行;
- 函数内部、类型定义内部、宏内部、初始化表达式内部,不使用连续空行
- 不使用连续 3 个空行,或更多
- 大括号内的代码块行首之前和行尾之后不要加空行。
int Foo() {
...
}
// Bad:两个函数定义间超过了一个空行
int Bar() {
...
}
if (...) {
// Bad:大括号内的代码块行首不要加入空行
...
// Bad:大括号内的代码块行尾不要加入空行
}
int Foo(...) {
// Bad:函数体内行首不要加空行
...
}
类
规则3.15.1 类访问控制块的声明依次序是 public:, protected:, private:,每个都缩进 1 个空格
class MyClass : public BaseClass {
public: // 注意没有缩进
MyClass(); // 标准的4空格缩进
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void SetVar(int var) { someVar = var; }
int GetVar() const { return someVar; }
private:
bool SomeInternalFunction();
int someVar;
int someOtherVar;
};
在各个部分中,建议将类似的声明放在一起, 并且建议以如下的顺序: 类型 (包括 typedef, using 和嵌套的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它成员函数, 数据成员。
规则3.15.2 构造函数初始化列表放在同一行或按四格缩进并排多行
// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : someVar(var) {
DoSomething();
}
// 如果不能放在同一行,
// 必须置于冒号后, 并缩进4个空格
MyClass::MyClass(int var)
: someVar(var), someOtherVar(var + 1) { // Good: 逗号后面留有空格
DoSomething();
}
// 如果初始化列表需要置于多行, 需要逐行对齐
MyClass::MyClass(int var)
: someVar(var), // 缩进4个空格
someOtherVar(var + 1) {
DoSomething();
}