格式化(Formatting)
编码风格和格式化相当随意,但如果每个人都使用相同的风格,项目就更容易遵循。个人可能不同意格式化规则的每个方面,有些规则可能需要一些时间来适应,但重要的是所有项目贡献者都遵循风格规则,以便他们都能轻松阅读和理解每个人的代码。
为了帮助你正确格式化代码,我们创建了一个 emacs 的设置文件 。
行长度
代码中每行文本最长应为 80 个字符。
我们认识到这条规则有争议,但如此多的现有代码已经遵循它,而且我们认为一致性很重要。
支持这条规则的人认为强迫他们调整窗口大小是不礼貌的,而且没有必要更长。有些人习惯于并排放置多个代码窗口,因此无论如何都没有空间加宽窗口。人们假设特定的最大窗口宽度来设置他们的工作环境,而 80 列一直是传统标准。为什么要改变它?
支持更改的人认为更宽的行可以使代码更具可读性。80 列限制是 1960 年代大型机的守旧遗留;现代设备有宽屏幕,可以轻松显示更长的行。
80 个字符是最大值。
一行可以超过 80 个字符,如果它是
- 一个注释行,如果拆分会损害可读性、复制粘贴的便利性或自动链接——例如,如果一行包含示例命令或超过 80 个字符的字面 URL。
- 一个无法在 80
列处轻松换行的字符串字面量。这可能是因为它包含 URI
或其他语义关键部分,或者因为字面量包含嵌入式语言,或者因为它是换行符有意义的多行字面量,如帮助消息。在这些情况下,拆分字面量会降低可读性、可搜索性、点击链接的能力等。除测试代码外,此类字面量应出现在文件顶部附近的命名空间作用域中。如果像
Clang-Format
这样的工具无法识别不可拆分的内容,请根据需要在内容周围禁用该工具 。
(我们必须在此类字面量的可用性/可搜索性与周围代码的可读性之间取得平衡。) - 一个 include 语句。
- 头文件保护符。
- 一个 using 声明。
非 ASCII 字符
非 ASCII 字符应该很少使用,且必须使用 UTF-8 编码。
你不应该在源代码中硬编码面向用户的文本,即使是英文也不行,因此非 ASCII 字符的使用应该很少。然而,在某些情况下,在代码中包含这些词是合适的。例如,如果你的代码解析来自外部来源的数据文件,硬编码这些数据文件中使用的非 ASCII 字符串作为分隔符可能是合适的。更常见的是,单元测试代码(不需要本地化)可能包含非 ASCII 字符串。在这些情况下,你应该使用 UTF-8,因为这是大多数能处理不仅仅是 ASCII 的工具所理解的编码。
十六进制编码也是可以的,在它增强可读性的地方鼓励使用——例如,"\xEF\xBB\xBF",或更简单的
"\uFEFF",是 Unicode 零宽不换行空格字符,如果作为原始 UTF-8
包含在源代码中将是不可见的。
尽可能避免使用 u8 前缀。它从 C++20 开始具有与 C++17
显著不同的语义,生成 char8_t 数组而不是 char,并且在 C++23
中还会再次改变。
你不应该使用 char16_t 和 char32_t 字符类型,因为它们用于非 UTF-8
文本。出于类似的原因,你也不应该使用 wchar_t(除非你编写的代码与
Windows API 交互,后者广泛使用 wchar_t)。
空格 vs. 制表符
仅使用空格,每次缩进 2 个空格。
我们使用空格进行缩进。不要在代码中使用制表符。你应该设置编辑器在按下 Tab 键时输出空格。
函数声明和定义
返回类型与函数名在同一行,如果放得下,参数也在同一行。将放不下一行的参数列表换行,如同你在函数调用中一样。
函数看起来像这样:
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 个空格的缩进。
从上下文中显而易见的未使用参数可以省略名称:
class Foo {
public:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};可能不明显的未使用参数应在函数定义中注释掉变量名:
class Shape {
public:
virtual void Rotate(double radians) = 0;
};
class Circle : public Shape {
public:
void Rotate(double radians) override;
};
void Circle::Rotate(double /*radians*/) {}// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}属性和展开为属性的宏出现在函数声明或定义的最开头,在返回类型之前:
ABSL_ATTRIBUTE_NOINLINE void ExpensiveFunction();
[[nodiscard]] bool IsOk();Lambda 表达式
像格式化任何其他函数一样格式化参数和函数体,捕获列表像其他逗号分隔的列表一样格式化。
对于按引用捕获,不要在 & 和变量名之间留空格。
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }短 Lambda 可以作为函数参数内联编写。
absl::flat_hash_set<int> to_remove = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&to_remove](int i) {
return to_remove.contains(i);
}),
digits.end());浮点字面量
浮点字面量应始终有小数点,两边都有数字,即使它们使用指数表示法。如果所有浮点字面量都采用这种熟悉的形式,可读性会提高,因为这有助于确保它们不会被误认为整数字面量,且指数表示法的
E/e 不会被误认为十六进制数字。用整数字面量初始化浮点变量是可以的(假设变量类型能精确表示该整数),但请注意指数表示法中的数字永远不是整数字面量。
float f = 1.f;
long double ld = -.5L;
double d = 1248e6;float f = 1.0f;
float f2 = 1.0; // Also OK
float f3 = 1; // Also OK
long double ld = -0.5L;
double d = 1248.0e6;函数调用
要么将调用全部写在一行上,在括号处换行参数,要么在新行开始参数并缩进四个空格并继续保持该 4 空格缩进。在没有其他考虑的情况下,使用最少的行数,包括在适当时在每行放置多个参数。
函数调用具有以下格式:
bool result = DoSomething(argument1, argument2, argument3);如果参数不能全部放在一行上,它们应该分成多行,每个后续行与第一个参数对齐。不要在左括号后或右括号前添加空格:
bool result = DoSomething(averyveryveryverylongargument1,
argument2, argument3);参数可以选择全部放在后续行上,使用四个空格缩进:
if (...) {
...
...
if (...) {
bool result = DoSomething(
argument1, argument2, // 4 space indent
argument3, argument4);
...
}将多个参数放在一行上以减少调用函数所需的行数,除非存在特定的可读性问题。有些人认为严格每行一个参数的格式更具可读性且简化了参数的编辑。然而,我们优先考虑读者而不是编辑参数的便利性,大多数可读性问题最好通过以下技术来解决。
如果一行中有多个参数由于组成某些参数的表达式的复杂性或令人困惑的特性而降低了可读性,尝试创建用描述性名称捕获这些参数的变量:
int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);或者将令人困惑的参数放在单独一行,并附上解释性注释:
bool result = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);如果仍然存在一个参数单独放在一行上明显更具可读性的情况,那就把它放在单独的行上。这个决定应该针对使其更具可读性的特定参数,而不是一般策略。
有时参数形成对可读性很重要的结构。在这些情况下,可以根据该结构自由格式化参数:
// Transform the widget by a 3x3 matrix.
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);花括号初始化列表格式
花括号初始化列表的格式与你在该位置格式化函数调用的方式完全相同。
如果花括号列表跟在名称(例如类型或变量名)之后,格式化时将 {}
视为以该名称进行函数调用的括号。如果没有名称,假设名称长度为零。
// Examples of braced init list on a single line.
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};
// When you have to wrap.
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 = { // Here, you could also break before {.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};循环和分支语句
在高层次上,循环或分支语句由以下组件组成:
- 一个或多个语句关键字(例如
if、else、switch、while、do或for)。 - 一个条件或迭代说明符,在括号内。
- 一个或多个受控语句,或受控语句块。
对于这些语句:
- 语句的组件应该用单个空格(而非换行)分隔。
- 在条件或迭代说明符内部,在每个分号和下一个标记之间放一个空格(或换行),除非该标记是右括号或另一个分号。
- 在条件或迭代说明符内部,不要在左括号后或右括号前放空格。
- 将所有受控语句放在块中(即使用花括号)。
- 在受控块内部,在左花括号后紧接一个换行,在右花括号前紧接一个换行。
if (condition) { // Good - no spaces inside parentheses, space before brace.
DoOneThing(); // Good - two-space indent.
DoAnotherThing();
} else if (int a = f(); a != 3) { // Good - closing brace on new line, else on same line.
DoAThirdThing(a);
} else {
DoNothing();
}
// Good - the same rules apply to loops.
while (condition) {
RepeatAThing();
}
// Good - the same rules apply to loops.
do {
RepeatAThing();
} while (condition);
// Good - the same rules apply to loops.
for (int i = 0; i < 10; ++i) {
RepeatAThing();
}if(condition) {} // Bad - space missing after `if`.
else if ( condition ) {} // Bad - space between the parentheses and the condition.
else if (condition){} // Bad - space missing before `{`.
else if(condition){} // Bad - multiple spaces missing.
for (int a = f();a == 10) {} // Bad - space missing after the semicolon.
// Bad - `if ... else` statement does not have braces everywhere.
if (condition)
foo;
else {
bar;
}
// Bad - `if` statement too long to omit braces.
if (condition)
// Comment
DoSomething();
// Bad - `if` statement too long to omit braces.
if (condition1 &&
condition2)
DoSomething();出于历史原因,我们允许上述规则有一个例外:如果结果是整个语句出现在一行上(此时右括号和受控语句之间有一个空格)或两行上(此时右括号后有一个换行且没有花括号),则可以省略受控语句的花括号或花括号内的换行。
// OK - fits on one line.
if (x == kFoo) { return new Foo(); }
// OK - braces are optional in this case.
if (x == kFoo) return new Foo();
// OK - condition fits on one line, body fits on another.
if (x == kBar)
Bar(arg1, arg2, arg3);此例外不适用于多关键字语句,如 if ... else 或 do ... while。
// Bad - `if ... else` statement is missing braces.
if (x) DoThis();
else DoThat();
// Bad - `do ... while` statement is missing braces.
do DoThis();
while (x);仅当语句简短时才使用这种风格,并考虑到具有复杂条件或受控语句的循环和分支语句使用花括号可能更具可读性。某些项目要求始终使用花括号。
switch 语句中的 case
块可以有也可以没有花括号,取决于你的偏好。如果你包含花括号,它们应如下所示放置。
switch (var) {
case 0: { // 2 space indent
Foo(); // 4 space indent
break;
}
default: {
Bar();
}
}空循环体应该使用一对空花括号或不带花括号的 continue,而不是单个分号。
while (condition) {} // Good - `{}` indicates no logic.
while (condition) {
// Comments are okay, too
}
while (condition) continue; // Good - `continue` indicates no logic.while (condition); // Bad - looks like part of `do-while` loop.指针和引用的表达式和类型
句点或箭头周围没有空格。指针运算符没有尾随空格。
以下是正确格式化的指针和引用表达式的示例:
x = *p;
p = &x;
x = r.y;
x = r->y;请注意:
- 访问成员时句点或箭头周围没有空格。
- 指针运算符在
*或&后没有空格。
在引用指针或引用(变量声明或定义、参数、返回类型、模板参数等)时,不能在星号/& 符号之前放空格。使用空格将类型与声明的名称(如果有)分隔开。
// These are fine.
char* c;
const std::string& str;
int* GetPointer();
std::vector<char*> // Note no space between '*' and '>'允许(虽然不常见)在同一声明中声明多个变量,但如果其中任何一个有指针或引用修饰,则不允许。这样的声明容易被误读。
// Fine if helpful for readability.
int x, y;int x, *y; // Disallowed - no & or * in multiple declaration
int *x, *y; // Disallowed - no & or * in multiple declaration
int *x; // Disallowed - & or * must be left of the space
char * c; // Bad - spaces on both sides of *
const std::string & str; // Bad - spaces on both sides of &布尔表达式
当布尔表达式超过标准行长度时,换行方式要保持一致。
在此示例中,逻辑 AND 运算符始终在行末:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}请注意,在此示例中代码换行时,两个 && 逻辑 AND
运算符都在行末。这在 Google
代码中更常见,尽管将所有运算符换行到行首也是允许的。可以适当地插入额外的括号,因为在适当使用时它们对提高可读性非常有帮助,但要注意不要过度使用。还请注意,你应该始终使用标点运算符(如
&& 和 ~),而不是单词运算符(如 and 和 compl)。
返回值
不要不必要地用括号包围 return 表达式。
仅在你会在 x = expr; 中使用括号的地方才在 return expr; 中使用括号。
return result; // No parentheses in the simple case.
// Parentheses OK to make a complex expression more readable.
return (some_long_condition &&
another_condition);return (value); // You wouldn't write var = (value);
return(result); // return is not a function!变量和数组初始化
你可以在 =、() 和 {} 之间选择;以下写法都是正确的:
int x = 3;
int x(3);
int x{3};
std::string name = "Some Name";
std::string name("Some Name");
std::string name{"Some Name"};在具有 std::initializer_list 构造函数的类型上使用花括号初始化列表
{...} 时要小心。非空的花括号初始化列表会尽可能优先选择
std::initializer_list 构造函数。请注意,空花括号 {}
是特殊的,如果可用的话会调用默认构造函数。要强制使用非
std::initializer_list 构造函数,请使用圆括号而不是花括号。
std::vector<int> v(100, 1); // A vector containing 100 items: All 1s.
std::vector<int> v{100, 1}; // A vector containing 2 items: 100 and 1.此外,花括号形式可以防止整数类型的窄化。这可以防止某些类型的编程错误。
int pi(3.14); // OK -- pi == 3.
int pi{3.14}; // Compile error: narrowing conversion.预处理器指令
开始预处理器指令的 # 号应始终在行的开头。
即使预处理器指令位于缩进代码的主体中,指令也应从行的开头开始。
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
# if NOTIFY // OK but not required -- Spaces after #
NotifyClient();
# endif
#endif
BackToNormal();
}// Bad - indented directives
if (lopsided_score) {
#if DISASTER_PENDING // Wrong! The "#if" should be at beginning of line
DropEverything();
#endif // Wrong! Do not indent "#endif"
BackToNormal();
}类格式
按 public、protected 和 private
顺序排列各部分,每部分缩进一个空格。
类定义的基本格式(不包含注释,关于需要什么注释的讨论请参阅类注释)是:
class MyClass : public OtherClass {
public: // Note the 1 space indent!
MyClass(); // Regular 2 space indent.
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
};需要注意的事项:
- 任何基类名称应与子类名称在同一行,受 80 列限制。
public:、protected:和private:关键字应缩进一个空格。- 除第一个实例外,这些关键字前应有一个空行。此规则在小型类中是可选的。
- 不要在这些关键字后留空行。
public部分应在前面,然后是protected,最后是private部分。- 关于每个部分内声明的排序规则,请参阅声明顺序。
构造函数初始化列表
构造函数初始化列表可以全部在一行上,或者后续行缩进四个空格。
初始化列表的可接受格式是:
// When everything fits on one line:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// If the signature and initializer list are not all on one line,
// you must wrap before the colon and indent 4 spaces:
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// When the list spans multiple lines, put each member on its own line
// and align them:
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
}
// As with any other code block, the close curly can be on the same
// line as the open curly, if it fits.
MyClass::MyClass(int var)
: some_var_(var) {}命名空间格式化
命名空间的内容不缩进。
命名空间不增加额外的缩进级别。例如,使用:
namespace {
void foo() { // Correct. No extra indentation within namespace.
...
}
} // namespace不要在命名空间内缩进:
namespace {
// Wrong! Indented when it should not be.
void foo() {
...
}
} // namespace水平空白
水平空白的使用取决于位置。永远不要在行尾放置尾随空白。
一般规则
int i = 0; // Two spaces before end-of-line comments.
void f(bool b) { // Open braces should always have a space before them.
...
int i = 0; // Semicolons usually have no space before them.
// Spaces inside braces for braced-init-list are optional. If you use them,
// put them on both sides!
int x[] = { 0 };
int x[] = {0};
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
public:
// For inline function implementations, put spaces between the braces
// and the implementation itself.
Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces.
void Reset() { baz_ = 0; } // Spaces separating braces from implementation.
...添加尾随空白可能会给编辑同一文件的其他人在合并时带来额外的工作,移除现有的尾随空白也是如此。因此,不要引入尾随空白。如果你已经在更改那一行,就移除它,或者在单独的清理操作中进行(最好是在没有其他人在处理该文件时)。
循环和条件语句
if (b) { // Space after the keyword in conditions and loops.
} else { // Spaces around else.
}
while (test) {} // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {
// Loops and conditions may have spaces inside parentheses, but this
// is rare. Be consistent.
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
// For loops always have a space after the semicolon. They may have a space
// before the semicolon, but this is rare.
for ( ; i < 5 ; ++i) {
...
// Range-based for loops always have a space before and after the colon.
for (auto x : counts) {
...
}
switch (i) {
case 1: // No space before colon in a switch case.
...
case 2: break; // Use a space after a colon if there's code after it.运算符
// Assignment operators always have spaces around them.
x = 0;
// Other binary operators usually have spaces around them, but it's
// OK to remove spaces around factors. Parentheses should have no
// internal padding.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// No spaces separating unary operators and their arguments.
x = -5;
++x;
if (x && !y)
...模板和类型转换
// No spaces inside the angle brackets (< and >), before
// <, or between >( in a cast
std::vector<std::string> x;
y = static_cast<char*>(x);
// No spaces between type and pointer.
std::vector<char*> x;垂直空白
谨慎使用垂直空白;不必要的空行使整体代码结构更难看清。仅在有助于读者理解结构的地方使用空行。
不要在缩进已经提供清晰划分的地方添加空行,例如在代码块的开头或结尾。确实应该使用空行将代码分隔成紧密相关的块,类似于散文中的段落分隔。在语句或声明中,通常只插入换行以保持在行长度限制内,或将注释附加到内容的一部分。