格式

尽管有些编程的排版风格因人而异,但是我们强烈建议和要求使用统一的编码风格,以便所有人都能够轻松的阅读和理解代码,增强代码的可维护性。

行宽

建议3.1.1 行宽不超过 120 个字符

建议每行字符数不要超过 120 个。如果超过120个字符,请选择合理的方式进行换行。

例外:

  • 如果一行注释包含了超过120 个字符的命令或URL,则可以保持一行,以方便复制、粘贴和通过grep查找;
  • 包含长路径的 #include 语句可以超出120 个字符,但是也需要尽量避免;
  • 编译预处理中的error信息可以超出一行。预处理的 error 信息在一行便于阅读和理解,即使超过 120 个字符。
1. #ifndef XXX_YYY_ZZZ
2. #error Header aaaa/bbbb/cccc/abc.h must only be included after xxxx/yyyy/zzzz/xyz.h, because xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3. #endif

缩进

规则3.2.1 使用空格进行缩进,每次缩进2个空格

只允许使用空格(space)进行缩进,每次缩进为 2 个空格。

大括号

规则3.3.1 除函数外,使用 K&R 缩进风格

K&R风格函数左大括号跟随语句放行末。右大括号独占一行,除非后面跟着同一语句的剩余部分,如 do 语句中的 while,或者 if 语句的 else/else if,或者逗号、分号。

如:

1. struct MyType {     // 跟随语句放行末,前置1空格
2. ...
3. };

5. int Foo(int a) {                   // 函数左大括号跟随语句放行末
6. if (...) {
7. ...
8. } else {
9. ...
10. }
11. }

推荐这种风格的理由:

  • 代码更紧凑;
  • 相比另起一行,放行末使代码阅读节奏感上更连续;
  • 符合后来语言的习惯,符合业界主流习惯;
  • 现代集成开发环境(IDE)都具有代码缩进对齐显示的辅助功能,大括号放在行尾并不会对缩进和范围产生理解上的影响。对于空函数体,可以将大括号放在同一行:
1. class MyClass {
2. public:
3. MyClass() : value(0) {}

5. private:
6. int value;
7. };

函数声明和定义

规则3.4.1 函数声明和定义的返回类型和函数名在同一行;函数参数列表超出行宽时要换行并合理对齐

在声明和定义函数的时候,函数的返回值类型应该和函数名在同一行;如果行宽度允许,函数参数也应该放在一行;否则,函数参数应该换行,并进行合理对齐。参数列表的左圆括号总是和函数名在同一行,不要单独一行;右圆括号总是跟随最后一个参数。

换行举例:

1. ReturnType FunctionName(ArgType paramName1, ArgType paramName2) {  // Good:全在同一行
2. ...
3. }

5. ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1,     // 行宽不满足所有参数,进行换行
6. ArgType paramName2,     // Good:和上一行参数对齐
7. ArgType paramName3) {
8. ...
9. }

11. ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // 行宽限制,进行换行
12. ArgType paramName3, ArgType paramName4, ArgType paramName5) {     // Good: 换行后 4 空格缩进
13. ...
14. }

16. ReturnType ReallyReallyReallyReallyLongFunctionName(            // 行宽不满足第1个参数,直接换行
17. ArgType paramName1, ArgType paramName2, ArgType paramName3) { // Good: 换行后 4 空格缩进
18. ...
19. }

函数调用

规则3.5.1 函数调用入参列表应放在一行,超出行宽换行时,保持参数进行合理对齐

函数调用时,函数参数列表放在一行。参数列表如果超过行宽,需要换行并进行合理的参数对齐。左圆括号总是跟函数名,右圆括号总是跟最后一个参数。

换行举例:

1. ReturnType result = FunctionName(paramName1, paramName2);   // Good:函数参数放在一行

3. ReturnType result = FunctionName(paramName1,
4. paramName2,                // Good:保持与上方参数对齐
5. paramName3);

7. ReturnType result = FunctionName(paramName1, paramName2,
8. paramName3, paramName4, paramName5);                    // Good:参数换行,4 空格缩进

10. ReturnType result = VeryVeryVeryLongFunctionName(           // 行宽不满足第1个参数,直接换行
11. paramName1, paramName2, paramName3);                    // 换行后,4 空格缩进

如果函数调用的参数存在内在关联性,按照可理解性优先于格式排版要求,对参数进行合理分组换行。

1. // Good:每行的参数代表一组相关性较强的数据结构,放在一行便于理解
2. int result = DealWithStructureLikeParams(left.x, left.y,     // 表示一组相关参数
3. right.x, right.y);  // 表示另外一组相关参数

if语句

规则3.6.1 if语句必须要使用大括号

我们要求if语句都需要使用大括号,即便只有一条语句。

理由:

  • 代码逻辑直观,易读;
  • 在已有条件语句代码上增加新代码时不容易出错;
  • 对于在if语句中使用函数式宏时,有大括号保护不易出错(如果宏定义时遗漏了大括号)。
1. if (objectIsNotExist) {         // Good:单行条件语句也加大括号
2. return CreateNewObject();
3. }

规则3.6.2 禁止 if/else/else if 写在同一行

条件语句中,若有多个分支,应该写在不同行。

如下是正确的写法:

1. if (someConditions) {
2. DoSomething();
3. ...
4. } else {  // Good: else 与 if 在不同行
5. ...
6. }

下面是不符合规范的案例:

1. if (someConditions) { ... } else { ... } // Bad: else 与 if 在同一行

循环语句

规则3.7.1 循环语句要求使用大括号

和if语句类似,我们要求for/while循环语句必须加上的大括号,即使循环体是空的,或者循环语句只有一条。

1. for (int i = 0; i < someRange; i++) {
2. DoSomething();
3. }

如果循环体是空的,应该使用空的大括号,而不是使用单个分号。 单个分号容易被遗漏,也容易被误认为是循环语句中的一部分。

1. for (int i = 0; i < someRange; i++) { } // Good: for循环体是空,使用大括号,而不是使用分号

3. while (someCondition) { }  // Good:while循环体是空,使用大括号,而不是使用分号

5. while (someCondition) {
6. continue;  // Good:continue表示空逻辑,可以使用大括号也可以不使用
7. }

坏的例子:

1. for (int i = 0; i < someRange; i++) ;   // Bad: for循环体是空,也不要只使用分号,要使用大括号

3. while (someCondition) ; // Bad:使用分号容易让人误解是while语句中的一部分

switch语句

规则3.8.1 switch 语句的 case/default 要缩进一层

switch 语句的缩进风格如下:

1. switch (var) {
2. case 0:             // Good: 缩进
3. DoSomething1();   // Good: 缩进
4. break;
5. case 1: {           // Good: 带大括号格式
6. DoSomething2();
7. break;
8. }
9. default:
10. break;
11. }
1. switch (var) {
2. case 0:                 // Bad: case 未缩进
3. DoSomething();
4. break;
5. default:                // Bad: default 未缩进
6. break;
7. }

表达式

建议3.9.1 表达式换行要保持换行的一致性,运算符放行末

较长的表达式,不满足行宽要求的时候,需要在适当的地方换行。一般在较低优先级运算符或连接符后面截断,运算符或连接符放在行末。运算符、连接符放在行末,表示“未结束,后续还有”。例:

// 假设下面第一行已经不满足行宽要求

1. if (currentValue > threshold &&  // Good:换行后,逻辑操作符放在行尾
2. someConditionsion) {
3. DoSomething();
4. ...
5. }

7. int result = reallyReallyLongVariableName1 +    // Good
8. reallyReallyLongVariableName2;

表达式换行后,注意保持合理对齐,或者4空格缩进。参考下面例子

1. int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
2. longVaribleName4 + longVaribleName5 + longVaribleName6;         // Good: 4空格缩进

4. int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
5. longVaribleName4 + longVaribleName5 + longVaribleName6;   // Good: 保持对齐

变量赋值

规则3.10.1 多个变量定义和赋值语句不允许写在一行

每行只有一个变量初始化的语句,更容易阅读和理解。

1. int maxCount = 10;
2. bool isCompleted = false;

下面是不符合规范的示例:

1. int maxCount = 10; bool isCompleted = false; // Bad:多个变量初始化需要分开放在多行,每行一个变量初始化
2. int x, y = 0;  // Bad:多个变量定义需要分行,每行一个

4. int pointX;
5. int pointY;
6. ...
7. pointX = 1; pointY = 2;  // Bad:多个变量赋值语句放同一行

例外:for 循环头、if 初始化语句(C++17)、结构化绑定语句(C++17)中可以声明和初始化多个变量。这些语句中的多个变量声明有较强关联,如果强行分成多行会带来作用域不一致,声明和初始化割裂等问题。

初始化

初始化包括结构体、联合体、及数组的初始化

规则3.11.1 初始化换行时要有缩进,并进行合理对齐

结构体或数组初始化时,如果换行应保持4空格缩进。从可读性角度出发,选择换行点和对齐位置。

1. const int rank[] = {
2. 16, 16, 16, 16, 32, 32, 32, 32,
3. 64, 64, 64, 64, 32, 32, 32, 32
4. };

指针与引用

建议3.12.1 指针类型"*"跟随变量名或者类型,不要两边都留有或者都没有空格

指针命名: *靠左靠右都可以,但是不要两边都有或者都没有空格。

1. int* p = NULL;  // Good
2. int *p = NULL;  // Good

4. int*p = NULL;   // Bad
5. int * p = NULL; // Bad

例外:当变量被 const 修饰时,"*" 无法跟随变量,此时也不要跟随类型。

1. char * const VERSION = "V100";

建议3.12.2 引用类型"&"跟随变量名或者类型,不要两边都留有或者都没有空格

引用命名:&靠左靠右都可以,但是不要两边都有或者都没有空格。

1. int i = 8;

3. int& p = i;     // Good
4. int &p = i;     // Good

6. int & p = i;    // Bad
7. int&p = i;      // Bad

编译预处理

规则3.13.1 编译预处理的"#"统一放在行首,嵌套编译预处理语句时,"#"不缩进

编译预处理的"#"统一放在行首,即使编译预处理的代码是嵌入在函数体中的,"#"也应该放在行首。

1. #if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) // Good:"#"放在行首
2. #define ATOMIC_X86_HAS_CMPXCHG16B 1  // Good:"#"放在行首
3. #else
4. #define ATOMIC_X86_HAS_CMPXCHG16B 0
5. #endif

8. int FunctionName() {
9. if (someThingError) {
10. ...
11. #ifdef HAS_SYSLOG        // Good:即便在函数内部,"#"也放在行首
12. WriteToSysLog();
13. #else
14. WriteToFileLog();
15. #endif
16. }
17. }

内嵌的预处理语句"#"不缩进

1. #if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
2. #define ATOMIC_X86_HAS_CMPXCHG16B 1  // Good:区分层次,便于阅读
3. #else
4. #define ATOMIC_X86_HAS_CMPXCHG16B 0
5. #endif

空格和空行

建议3.14.1 水平空格应该突出关键字和重要信息,避免不必要的留白

水平空格应该突出关键字和重要信息,每行代码尾部不要加空格。总体规则如下:

  • if, switch, case, do, while, for等关键字之后加空格;
  • 小括号内部的两侧,不要加空格;
  • 大括号内部两侧有无空格,左右必须保持一致;
  • 一元操作符(& * + ‐ ~ !)之后不要加空格;
  • 二元操作符(= + ‐ < > * / % | & ^ <= >= == != )左右两侧加空格
  • 三目运算符(? :)符号两侧均需要空格
  • 前置和后置的自增、自减(++ —)和变量之间不加空格
  • 结构体成员操作符(. ->)前后不加空格
  • 逗号(,)前面不加空格,后面增加空格
  • 对于模板和类型转换(<>)和类型之间不要添加空格
  • 域操作符(::)前后不要添加空格
  • 冒号(:)前后根据情况来判断是否要添加空格常规情况:
1. void Foo(int b) {  // Good:大括号前应该留空格

3. int i = 0;  // Good:变量初始化时,=前后应该有空格,分号前面不要留空格

5. int buf[kBufSize] = {0};    // Good:大括号内两侧都无空格

函数定义和函数调用:

1. int result = Foo(arg1,arg2);
2. ^    // Bad: 逗号后面需要增加空格

4. int result = Foo( arg1, arg2 );
5. ^          ^  // Bad: 函数参数列表的左括号后面不应该有空格,右括号前面不应该有空格

指针和取地址

1. x = *p;     // Good:*操作符和指针p之间不加空格
2. p = &x;     // Good:&操作符和变量x之间不加空格
3. x = r.y;    // Good:通过.访问成员变量时不加空格
4. x = r->y;   // Good:通过->访问成员变量时不加空格

操作符:

1. x = 0;   // Good:赋值操作的=前后都要加空格
2. x = -5;  // Good:负数的符号和数值之前不要加空格
3. ++x;     // Good:前置和后置的++/--和变量之间不要加空格
4. x--;

6. if (x && !y)  // Good:布尔操作符前后要加上空格,!操作和变量之间不要空格
7. v = w * x + y / z;  // Good:二元操作符前后要加空格
8. v = w * (x + z);    // Good:括号内的表达式前后不需要加空格

10. int a = (x < y) ? x : y;  // Good: 三目运算符, ?和:前后需要添加空格

循环和条件语句:

1. if (condition) {  // Good:if关键字和括号之间加空格,括号内条件语句前后不加空格
2. ...
3. } else {           // Good:else关键字和大括号之间加空格
4. ...
5. }

7. while (condition) {}   // Good:while关键字和括号之间加空格,括号内条件语句前后不加空格

9. for (int i = 0; i < someRange; ++i) {  // Good:for关键字和括号之间加空格,分号之后加空格
10. ...
11. }

13. switch (condition) {  // Good: switch 关键字后面有1空格
14. case 0:     // Good:case语句条件和冒号之间不加空格
15. ...
16. break;
17. ...
18. default:
19. ...
20. break;
21. }

模板和转换

1. // 尖括号(< and >) 不与空格紧邻, < 前没有空格, > 和 ( 之间也没有.
2. vector<string> x;
3. y = static_cast<char*>(x);

5. // 在类型与指针操作符之间留空格也可以, 但要保持一致.
6. vector<char *> x;

域操作符

1. std::cout;    // Good: 命名空间访问,不要留空格

3. int MyClass::GetValue() const {}  // Good: 对于成员函数定义,不要留空格

冒号

1. // 添加空格的场景

3. // Good: 类的派生需要留有空格
4. class Sub : public Base {

6. };

8. // 构造函数初始化列表需要留有空格
9. MyClass::MyClass(int var) : someVar(var) {
10. DoSomething();
11. }

13. // 位域表示也留有空格
14. struct XX {
15. char a : 4;
16. char b : 5;
17. char c : 4;
18. };
1. // 不添加空格的场景

3. // Good: 对于public:, private:这种类访问权限的冒号不用添加空格
4. class MyClass {
5. public:
6. MyClass(int var);
7. private:
8. int someVar;
9. };

11. // 对于switch-case的case和default后面的冒号不用添加空格
12. switch (value) {
13. case 1:
14. DoSomething();
15. break;
16. default:
17. break;
18. }

注意:当前的集成开发环境(IDE)可以设置删除行尾的空格,请正确配置。

建议3.14.2 合理安排空行,保持代码紧凑

减少不必要的空行,可以显示更多的代码,方便代码阅读。下面有一些建议遵守的规则:

  • 根据上下内容的相关程度,合理安排空行;
  • 函数内部、类型定义内部、宏内部、初始化表达式内部,不使用连续空行
  • 不使用连续 3 个空行,或更多
  • 大括号内的代码块行首之前和行尾之后不要加空行。
1. int Foo() {
2. ...
3. }

6. // Bad:两个函数定义间超过了一个空行
7. int Bar() {
8. ...
9. }

12. if (...) {
13. // Bad:大括号内的代码块行首不要加入空行
14. ...
15. // Bad:大括号内的代码块行尾不要加入空行
16. }

18. int Foo(...) {
19. // Bad:函数体内行首不要加空行
20. ...
21. }

类

规则3.15.1 类访问控制块的声明依次序是 public:, protected:, private:,每个都缩进 1 个空格

1. class MyClass : public BaseClass {
2. public:      // 注意没有缩进
3. MyClass();  // 标准的4空格缩进
4. explicit MyClass(int var);
5. ~MyClass() {}

7. void SomeFunction();
8. void SomeFunctionThatDoesNothing() {
9. }

11. void SetVar(int var) { someVar = var; }
12. int GetVar() const { return someVar; }

14. private:
15. bool SomeInternalFunction();

17. int someVar;
18. int someOtherVar;
19. };

在各个部分中,建议将类似的声明放在一起, 并且建议以如下的顺序: 类型 (包括 typedef, using 和嵌套的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它成员函数, 数据成员。

规则3.15.2 构造函数初始化列表放在同一行或按四格缩进并排多行

1. // 如果所有变量能放在同一行:
2. MyClass::MyClass(int var) : someVar(var) {
3. DoSomething();
4. }

6. // 如果不能放在同一行,
7. // 必须置于冒号后, 并缩进4个空格
8. MyClass::MyClass(int var)
9. : someVar(var), someOtherVar(var + 1) { // Good: 逗号后面留有空格
10. DoSomething();
11. }

13. // 如果初始化列表需要置于多行, 需要逐行对齐
14. MyClass::MyClass(int var)
15. : someVar(var),             // 缩进4个空格
16. someOtherVar(var + 1) {
17. DoSomething();
18. }