格式

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

行宽

建议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. };
  4. int Foo(int a) { // 函数左大括号跟随语句放行末
  5. if (...) {
  6. ...
  7. } else {
  8. ...
  9. }
  10. }

推荐这种风格的理由:

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

函数声明和定义

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

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

换行举例:

  1. ReturnType FunctionName(ArgType paramName1, ArgType paramName2) { // Good:全在同一行
  2. ...
  3. }
  4. ReturnType VeryVeryVeryLongFunctionName(ArgType paramName1, // 行宽不满足所有参数,进行换行
  5. ArgType paramName2, // Good:和上一行参数对齐
  6. ArgType paramName3) {
  7. ...
  8. }
  9. ReturnType LongFunctionName(ArgType paramName1, ArgType paramName2, // 行宽限制,进行换行
  10. ArgType paramName3, ArgType paramName4, ArgType paramName5) { // Good: 换行后 4 空格缩进
  11. ...
  12. }
  13. ReturnType ReallyReallyReallyReallyLongFunctionName( // 行宽不满足第1个参数,直接换行
  14. ArgType paramName1, ArgType paramName2, ArgType paramName3) { // Good: 换行后 4 空格缩进
  15. ...
  16. }

函数调用

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

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

换行举例:

  1. ReturnType result = FunctionName(paramName1, paramName2); // Good:函数参数放在一行
  2. ReturnType result = FunctionName(paramName1,
  3. paramName2, // Good:保持与上方参数对齐
  4. paramName3);
  5. ReturnType result = FunctionName(paramName1, paramName2,
  6. paramName3, paramName4, paramName5); // Good:参数换行,4 空格缩进
  7. ReturnType result = VeryVeryVeryLongFunctionName( // 行宽不满足第1个参数,直接换行
  8. 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循环体是空,使用大括号,而不是使用分号
  2. while (someCondition) { } // Good:while循环体是空,使用大括号,而不是使用分号
  3. while (someCondition) {
  4. continue; // Good:continue表示空逻辑,可以使用大括号也可以不使用
  5. }

坏的例子:

  1. for (int i = 0; i < someRange; i++) ; // Bad: for循环体是空,也不要只使用分号,要使用大括号
  2. 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. }
  6. int result = reallyReallyLongVariableName1 + // Good
  7. reallyReallyLongVariableName2;

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

  1. int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
  2. longVaribleName4 + longVaribleName5 + longVaribleName6; // Good: 4空格缩进
  3. int sum = longVaribleName1 + longVaribleName2 + longVaribleName3 +
  4. 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:多个变量定义需要分行,每行一个
  3. int pointX;
  4. int pointY;
  5. ...
  6. 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
  3. int*p = NULL; // Bad
  4. int * p = NULL; // Bad

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

  1. char * const VERSION = "V100";

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

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

  1. int i = 8;
  2. int& p = i; // Good
  3. int &p = i; // Good
  4. int & p = i; // Bad
  5. 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
  6. int FunctionName() {
  7. if (someThingError) {
  8. ...
  9. #ifdef HAS_SYSLOG // Good:即便在函数内部,"#"也放在行首
  10. WriteToSysLog();
  11. #else
  12. WriteToFileLog();
  13. #endif
  14. }
  15. }

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

  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:大括号前应该留空格
  2. int i = 0; // Good:变量初始化时,=前后应该有空格,分号前面不要留空格
  3. int buf[kBufSize] = {0}; // Good:大括号内两侧都无空格

函数定义和函数调用:

  1. int result = Foo(arg1,arg2);
  2. ^ // Bad: 逗号后面需要增加空格
  3. int result = Foo( arg1, arg2 );
  4. ^ ^ // 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--;
  5. if (x && !y) // Good:布尔操作符前后要加上空格,!操作和变量之间不要空格
  6. v = w * x + y / z; // Good:二元操作符前后要加空格
  7. v = w * (x + z); // Good:括号内的表达式前后不需要加空格
  8. int a = (x < y) ? x : y; // Good: 三目运算符, ?和:前后需要添加空格

循环和条件语句:

  1. if (condition) { // Good:if关键字和括号之间加空格,括号内条件语句前后不加空格
  2. ...
  3. } else { // Good:else关键字和大括号之间加空格
  4. ...
  5. }
  6. while (condition) {} // Good:while关键字和括号之间加空格,括号内条件语句前后不加空格
  7. for (int i = 0; i < someRange; ++i) { // Good:for关键字和括号之间加空格,分号之后加空格
  8. ...
  9. }
  10. switch (condition) { // Good: switch 关键字后面有1空格
  11. case 0: // Good:case语句条件和冒号之间不加空格
  12. ...
  13. break;
  14. ...
  15. default:
  16. ...
  17. break;
  18. }

模板和转换

  1. // 尖括号(< and >) 不与空格紧邻, < 前没有空格, > 和 ( 之间也没有.
  2. vector<string> x;
  3. y = static_cast<char*>(x);
  4. // 在类型与指针操作符之间留空格也可以, 但要保持一致.
  5. vector<char *> x;

域操作符

  1. std::cout; // Good: 命名空间访问,不要留空格
  2. int MyClass::GetValue() const {} // Good: 对于成员函数定义,不要留空格

冒号

  1. // 添加空格的场景
  2. // Good: 类的派生需要留有空格
  3. class Sub : public Base {
  4. };
  5. // 构造函数初始化列表需要留有空格
  6. MyClass::MyClass(int var) : someVar(var) {
  7. DoSomething();
  8. }
  9. // 位域表示也留有空格
  10. struct XX {
  11. char a : 4;
  12. char b : 5;
  13. char c : 4;
  14. };
  1. // 不添加空格的场景
  2. // Good: 对于public:, private:这种类访问权限的冒号不用添加空格
  3. class MyClass {
  4. public:
  5. MyClass(int var);
  6. private:
  7. int someVar;
  8. };
  9. // 对于switch-case的case和default后面的冒号不用添加空格
  10. switch (value) {
  11. case 1:
  12. DoSomething();
  13. break;
  14. default:
  15. break;
  16. }

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

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

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

  • 根据上下内容的相关程度,合理安排空行;
  • 函数内部、类型定义内部、宏内部、初始化表达式内部,不使用连续空行
  • 不使用连续 3 个空行,或更多
  • 大括号内的代码块行首之前和行尾之后不要加空行。
  1. int Foo() {
  2. ...
  3. }
  4. // Bad:两个函数定义间超过了一个空行
  5. int Bar() {
  6. ...
  7. }
  8. if (...) {
  9. // Bad:大括号内的代码块行首不要加入空行
  10. ...
  11. // Bad:大括号内的代码块行尾不要加入空行
  12. }
  13. int Foo(...) {
  14. // Bad:函数体内行首不要加空行
  15. ...
  16. }

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

  1. class MyClass : public BaseClass {
  2. public: // 注意没有缩进
  3. MyClass(); // 标准的4空格缩进
  4. explicit MyClass(int var);
  5. ~MyClass() {}
  6. void SomeFunction();
  7. void SomeFunctionThatDoesNothing() {
  8. }
  9. void SetVar(int var) { someVar = var; }
  10. int GetVar() const { return someVar; }
  11. private:
  12. bool SomeInternalFunction();
  13. int someVar;
  14. int someOtherVar;
  15. };

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

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

  1. // 如果所有变量能放在同一行:
  2. MyClass::MyClass(int var) : someVar(var) {
  3. DoSomething();
  4. }
  5. // 如果不能放在同一行,
  6. // 必须置于冒号后, 并缩进4个空格
  7. MyClass::MyClass(int var)
  8. : someVar(var), someOtherVar(var + 1) { // Good: 逗号后面留有空格
  9. DoSomething();
  10. }
  11. // 如果初始化列表需要置于多行, 需要逐行对齐
  12. MyClass::MyClass(int var)
  13. : someVar(var), // 缩进4个空格
  14. someOtherVar(var + 1) {
  15. DoSomething();
  16. }