- 格式
- 行宽
- 建议3.1.1 行宽不超过 120 个字符
- 缩进
- 规则3.2.1 使用空格进行缩进,每次缩进2个空格
- 大括号
- 规则3.3.1 除函数外,使用 K&R 缩进风格
- 函数声明和定义
- 规则3.4.1 函数声明和定义的返回类型和函数名在同一行;函数参数列表超出行宽时要换行并合理对齐
- 函数调用
- 规则3.5.1 函数调用入参列表应放在一行,超出行宽换行时,保持参数进行合理对齐
- if语句
- 规则3.6.1 if语句必须要使用大括号
- 规则3.6.2 禁止 if/else/else if 写在同一行
- 循环语句
- 规则3.7.1 循环语句要求使用大括号
- switch语句
- 规则3.8.1 switch 语句的 case/default 要缩进一层
- 表达式
- 建议3.9.1 表达式换行要保持换行的一致性,运算符放行末
- 变量赋值
- 规则3.10.1 多个变量定义和赋值语句不允许写在一行
- 初始化
- 规则3.11.1 初始化换行时要有缩进,并进行合理对齐
- 指针与引用
- 建议3.12.1 指针类型"*"跟随变量名或者类型,不要两边都留有或者都没有空格
- 建议3.12.2 引用类型"&"跟随变量名或者类型,不要两边都留有或者都没有空格
- 编译预处理
- 规则3.13.1 编译预处理的"#"统一放在行首,嵌套编译预处理语句时,"#"不缩进
- 空格和空行
- 建议3.14.1 水平空格应该突出关键字和重要信息,避免不必要的留白
- 建议3.14.2 合理安排空行,保持代码紧凑
- 类
- 规则3.15.1 类访问控制块的声明依次序是 public:, protected:, private:,每个都缩进 1 个空格
- 规则3.15.2 构造函数初始化列表放在同一行或按四格缩进并排多行
- 行宽
格式
尽管有些编程的排版风格因人而异,但是我们强烈建议和要求使用统一的编码风格,以便所有人都能够轻松的阅读和理解代码,增强代码的可维护性。
行宽
建议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 + // GoodreallyReallyLongVariableName2;
表达式换行后,注意保持合理对齐,或者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; // Goodint *p = NULL; // Goodint*p = NULL; // Badint * p = NULL; // Bad
例外:当变量被 const 修饰时,"*" 无法跟随变量,此时也不要跟随类型。
char * const VERSION = "V100";
建议3.12.2 引用类型"&"跟随变量名或者类型,不要两边都留有或者都没有空格
引用命名:&靠左靠右都可以,但是不要两边都有或者都没有空格。
int i = 8;int& p = i; // Goodint &p = i; // Goodint & p = i; // Badint&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#endifint FunctionName() {if (someThingError) {...#ifdef HAS_SYSLOG // Good:即便在函数内部,"#"也放在行首WriteToSysLog();#elseWriteToFileLog();#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();}
