Google C++编程风格整理(二)

5.其他 C++ 特性
5.1. 引用参数

Tip
所以按引用传递的参数必须加上 const.

定义:
在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval). 在 C++ 中, 函数还可以声明引用参数: int foo(int &val).

优点:
定义引用参数防止出现 (*pval)++ 这样丑陋的代码. 像拷贝构造函数这样的应用也是必需的. 而且更明确, 不接受 NULL 指针.

缺点:
容易引起误解, 因为引用在语法上是值变量却拥有指针的语义.

结论:
函数参数列表中, 所有引用参数都必须是 const:

void Foo(const string &in, string *out);

事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是 非 const 的引用参数.

在以下情况你可以把输入参数定义为 const 指针: 你想强调参数不是拷贝而来的, 在对象生存周期内必须一直存在; 最好同时在注释中详细说明一下. bind2nd 和 mem_fun 等 STL 适配器不接受引用参数, 这种情况下你也必须把函数参数声明成指针类型.

5.2. 函数重载

Tip
仅在输入参数类型不同, 功能相同时使用重载函数 (含构造函数). 不要用函数重载模拟 缺省函数参数.

定义:
你可以编写一个参数类型为 const string& 的函数, 然后用另一个参数类型为 const char* 的函数重载它:

class MyClass {
    public:
    void Analyze(const string &text);
    void Analyze(const char *text, size_t textlen);
};

优点:
通过重载参数不同的同名函数, 令代码更加直观. 模板化代码需要重载, 同时为使用者带来便利.

缺点:
限制使用重载的一个原因是在某个特定调用点很难确定到底调用的是哪个函数. 另一个原因是当派生类只重载了某个函数的部分变体, 会令很多人对继承的语义产生困惑. 此外在阅读库的用户代码时, 可能会因反对使用 缺省函数参数 造成不必要的费解.

结论:
如果你想重载一个函数, 考虑让函数名包含参数信息, 例如, 使用 AppendString(), AppendInt() 而不是 Append().

5.3. 缺省参数

Tip
我们不允许使用缺省函数参数.

优点:
多数情况下, 你写的函数可能会用到很多的缺省值, 但偶尔你也会修改这些缺省值. 无须为了这些偶尔情况定义很多的函数, 用缺省参数就能很轻松的做到这点.

缺点:
大家通常都是通过查看别人的代码来推断如何使用 API. 用了缺省参数的代码更难维护, 从老代码复制粘贴而来的新代码可能只包含部分参数. 当缺省参数不适用于新代码时可能会导致重大问题.

结论:
我们规定所有参数必须明确指定, 迫使程序员理解 API 和各参数值的意义, 避免默默使用他们可能都还没意识到的缺省参数.

5.4. 变长数组和 alloca()

Tip
我们不允许使用变长数组和 alloca().

优点:
变长数组具有浑然天成的语法. 变长数组和 alloca() 也都很高效.

缺点:
变长数组和 alloca() 不是标准 C++ 的组成部分. 更重要的是, 它们根据数据大小动态分配堆栈内存, 会引起难以发现的内存越界 bugs: “在我的机器上运行的好好的, 发布后却莫名其妙的挂掉了”.

结论:
使用安全的内存分配器, 如 scoped_ptr / scoped_array.

5.5. 友元

Tip
我们允许合理的使用友元类及友元函数.

通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类. 经常用到友元的一个地方是将 FooBuilder 声明为 Foo 的友元, 以便 FooBuilder 正确构造 Foo 的内部状态, 而无需将该状态暴露出来. 某些情况下, 将一个单元测试类声明成待测类的友元会很方便.

友元扩大了 (但没有打破) 类的封装边界. 某些情况下, 相对于将类成员声明为 public, 使用友元是更好的选择, 尤其是如果你只允许另一个类访问该类的私有成员时. 当然, 大多数类都只应该通过其提供的公有成员进行互操作.

5.6. 异常

Tip
我们不使用 C++ 异常.

优点:
•异常允许上层应用决定如何处理在底层嵌套函数中 “不可能出现的” 失败, 不像错误码记录那么含糊又易出错;
•很多现代语言都使用异常. 引入异常使得 C++ 与 Python, Java 以及其它 C++ 相近的语言更加兼容.
•许多第三方 C++ 库使用异常, 禁用异常将导致很难集成这些库.
•异常是处理构造函数失败的唯一方法. 虽然可以通过工厂函数或 Init() 方法替代异常, 但他们分别需要堆分配或新的 “无效” 状态;
•在测试框架中使用异常确实很方便.

缺点:
•在现有函数中添加 throw 语句时, 你必须检查所有调用点. 所有调用点得至少有基本的异常安全保护, 否则永远捕获不到异常, 只好 “开心的” 接受程序终止的结果. 例如, 如果 f() 调用了 g(), g() 又调用了 h(), h 抛出的异常被 f 捕获, g 要当心了, 很可能会因疏忽而未被妥善清理.
•更普遍的情况是, 如果使用异常, 光凭查看代码是很难评估程序的控制流: 函数返回点可能在你意料之外. 这回导致代码管理和调试困难. 你可以通过规定何时何地如何使用异常来降低开销, 但是让开发人员必须掌握并理解这些规定带来的代价更大.
•异常安全要求同时采用 RAII 和不同编程实践. 要想轻松编写正确的异常安全代码, 需要大量的支撑机制配合. 另外, 要避免代码读者去理解整个调用结构图, 异常安全代码必须把写持久化状态的逻辑部分隔离到 “提交” 阶段. 它在带来好处的同时, 还有成本 (也许你不得不为了隔离 “提交” 而整出令人费解的代码). 允许使用异常会驱使我们不断为此付出代价, 即使我们觉得这很不划算.
•启用异常使生成的二进制文件体积变大, 延长了编译时间 (或许影响不大), 还可能增加地址空间压力;
•异常的实用性可能会怂恿开发人员在不恰当的时候抛出异常, 或者在不安全的地方从异常中恢复. 例如, 处理非法用户输入时就不应该抛出异常. 如果我们要完全列出这些约束, 这份风格指南会长出很多!

结论:
从表面上看, 使用异常利大于弊, 尤其是在新项目中. 但是对于现有代码, 引入异常会牵连到所有相关代码. 如果新项目允许异常向外扩散, 在跟以前未使用异常的代码整合时也将是个麻烦. 因为 Google 现有的大多数 C++ 代码都没有异常处理, 引入带有异常处理的新代码相当困难.

鉴于 Google 现有代码不接受异常, 在现有代码中使用异常比在新项目中使用的代价多少要大一些. 迁移过程比较慢, 也容易出错. 我们不相信异常的使用有效替代方案, 如错误代码, 断言等会造成严重负担.

我们并不是基于哲学或道德层面反对使用异常, 而是在实践的基础上. 我们希望在 Google 使用我们自己的开源项目, 但项目中使用异常会为此带来不便, 因此我们也建议不要在 Google 的开源项目中使用异常. 如果我们需要把这些项目推倒重来显然不太现实.

(注: 对于异常处理, 显然不是短短几句话能够说清楚的, 以构造函数为例, 很多 C++ 书籍上都提到当构造失败时只有异常可以处理, Google 禁止使用异常这一点, 仅仅是为了自身的方便, 说大了, 无非是基于软件管理成本上, 实际使用中还是自己决定)

5.7. 运行时类型识别

Tip
我们禁止使用 RTTI.

定义:
RTTI 允许程序员在运行时识别 C++ 类对象的类型.

优点:
RTTI 在某些单元测试中非常有用. 比如进行工厂类测试时, 用来验证一个新建对象是否为期望的动态类型.
除测试外, 极少用到.

缺点:
在运行时判断类型通常意味着设计问题. 如果你需要在运行期间确定一个对象的类型, 这通常说明你需要考虑重新设计你的类.

结论:
除单元测试外, 不要使用 RTTI. 如果你发现自己不得不写一些行为逻辑取决于对象类型的代码, 考虑换一种方式判断对象类型.

如果要实现根据子类类型来确定执行不同逻辑代码, 虚函数无疑更合适. 在对象内部就可以处理类型识别问题.

如果要在对象外部的代码中判断类型, 考虑使用双重分派方案, 如访问者模式. 可以方便的在对象本身之外确定类的类型.

如果你认为上面的方法你真的掌握不了, 你可以使用 RTTI, 但务必请三思 :-) . 不要试图手工实现一个貌似 RTTI 的替代方案, 我们反对使用 RTTI 的理由, 同样适用于那些在类型继承体系上使用类型标签的替代方案.

5.8. 类型转换

Tip
使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式;

定义:
C++ 采用了有别于 C 的类型转换机制, 对转换操作进行归类.

优点:
C 语言的类型转换问题在于模棱两可的操作; 有时是在做强制转换 (如 (int)3.5), 有时是在做类型转换 (如 (int)”hello”). 另外, C++ 的类型转换在查找时更醒目.

缺点:
恶心的语法.

结论:
不要使用 C 风格类型转换. 而应该使用 C++ 风格.
•用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.
•用 const_cast 去掉 const 限定符.
•用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.
•dynamic_cast 测试代码以外不要使用. 除非是单元测试, 如果你需要在运行时确定类型信息, 说明有 设计缺陷.

5.9. 流

Tip
只在记录日志时使用流.

定义:
流用来替代 printf() 和 scanf().

优点:
有了流, 在打印时不需要关心对象的类型. 不用担心格式化字符串与参数列表不匹配 (虽然在 gcc 中使用 printf 也不存在这个问题). 流的构造和析构函数会自动打开和关闭对应的文件.

缺点:
流使得 pread() 等功能函数很难执行. 如果不使用 printf 风格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s) 用流处理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而这一点对于软件国际化很有用.

结论:
不要使用流, 除非是日志接口需要. 使用 printf 之类的代替.
使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.

拓展讨论:
对这一条规则存在一些争论, 这儿给出点深层次原因. 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致. 因此, 我们不希望用户来决定是使用流还是 printf + read/write. 相反, 我们应该决定到底用哪一种方式. 把日志作为特例是因为日志是一个非常独特的应用, 还有一些是历史原因.

流的支持者们主张流是不二之选, 但观点并不是那么清晰有力. 他们指出的流的每个优势也都是其劣势. 流最大的优势是在输出时不需要关心打印对象的类型. 这是一个亮点. 同时, 也是一个不足: 你很容易用错类型, 而编译器不会报警. 使用流时容易造成的这类错误:

cout << this;   // Prints the address
cout << *this;  // Prints the contents

由于 << 被重载, 编译器不会报错. 就因为这一点我们反对使用操作符重载.
有人说 printf 的格式化丑陋不堪, 易读性差, 但流也好不到哪儿去. 看看下面两段代码吧, 实现相同的功能, 哪个更清晰?

cerr << "Error connecting to '" << foo->bar()->hostname.first
     << ":" << foo->bar()->hostname.second << ": " << strerror(errno);

fprintf(stderr, "Error connecting to '%s:%u: %s",
        foo->bar()->hostname.first, foo->bar()->hostname.second,
        strerror(errno));

你可能会说, “把流封装一下就会比较好了”, 这儿可以, 其他地方呢? 而且不要忘了, 我们的目标是使语言更紧凑, 而不是添加一些别人需要学习的新装备.

每一种方式都是各有利弊, “没有最好, 只有更适合”. 简单性原则告诫我们必须从中选择其一, 最后大多数决定采用 printf + read/write.

5.10. 前置自增和自减

Tip
对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.

定义:
对于变量在自增 (++i 或 i++) 或自减 (–i 或 i–) 后表达式的值又没有没用到的情况下, 需要确定到底是使用前置还是后置的自增 (自减).

优点:
不考虑返回值的话, 前置自增 (++i) 通常要比后置自增 (i++) 效率更高. 因为后置自增 (或自减) 需要对表达式的值 i 进行一次拷贝. 如果 i 是迭代器或其他非数值类型, 拷贝的代价是比较大的. 既然两种自增方式实现的功能一样, 为什么不总是使用前置自增呢?

缺点:
在 C 开发中, 当表达式的值未被使用时, 传统的做法是使用后置自增, 特别是在 for 循环中. 有些人觉得后置自增更加易懂, 因为这很像自然语言, 主语 (i) 在谓语动词 (++) 前.

结论:
对简单数值 (非对象), 两种都无所谓. 对迭代器和模板类型, 使用前置自增 (自减).

5.11. const 的使用

Tip
我们强烈建议你在任何可能的情况下都要使用 const.

定义:
在声明的变量或参数前加上关键字 const 用于指明变量值不可被篡改 (如 const int foo ). 为类中的函数加上 const 限定符表明该函数不会修改类成员变量的状态 (如 class Foo { int Bar(char c) const; };).

优点:
大家更容易理解如何使用变量. 编译器可以更好地进行类型检测, 相应地, 也能生成更好的代码. 人们对编写正确的代码更加自信, 因为他们知道所调用的函数被限定了能或不能修改变量值. 即使是在无锁的多线程编程中, 人们也知道什么样的函数是安全的.

缺点:
const 是入侵性的: 如果你向一个函数传入 const 变量, 函数原型声明中也必须对应 const 参数 (否则变量需要 const_cast 类型转换), 在调用库函数时显得尤其麻烦.

结论:
const 变量, 数据成员, 函数和参数为编译时类型检测增加了一层保障; 便于尽早发现错误. 因此, 我们强烈建议在任何可能的情况下使用 const:
•如果函数不会修改传入的引用或指针类型参数, 该参数应声明为 const.
•尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.
•如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.

然而, 也不要发了疯似的使用 const. 像 const int * const * const x; 就有些过了, 虽然它非常精确的描述了常量 x. 关注真正有帮助意义的信息: 前面的例子写成 const int** x 就够了.

关键字 mutable 可以使用, 但是在多线程中是不安全的, 使用时首先要考虑线程安全.

const 的位置:
有人喜欢 int const foo 形式, 不喜欢 const int foo, 他们认为前者更一致因此可读性也更好: 遵循了 const 总位于其描述的对象之后的原则. 但是一致性原则不适用于此, “不要过度使用” 的声明可以取消大部分你原本想保持的一致性. 将 const 放在前面才更易读, 因为在自然语言中形容词 (const) 是在名词 (int) 之前.

这是说, 我们提倡但不强制 const 在前. 但要保持代码的一致性! (注: 也就是不要在一些地方把 const 写在类型前面, 在其他地方又写在后面, 确定一种写法, 然后保持一致.)

5.12. 整型

Tip
C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t.

定义:
C++ 没有指定整型的大小. 通常人们假定 short 是 16 位, int是 32 位, long 是 32 位, long long 是 64 位.

优点:
保持声明统一.

缺点:
C++ 中整型大小因编译器和体系结构的不同而不同.

结论:

<stdint.h> 定义了 int16_t, uint32_t, int64_t 等整型, 在需要确保整型大小时可以使用它们代替 short, unsigned long long 等. 在 C 整型中, 只使用 int. 在合适的情况下, 推荐使用标准类型如 size_t 和 ptrdiff_t.

如果已知整数不会太大, 我们常常会使用 int, 如循环计数. 在类似的情况下使用原生类型 int. 你可以认为 int 至少为 32 位, 但不要认为它会多于 32 位. 如果需要 64 位整型, 用 int64_t 或 uint64_t.对于大整数, 使用 int64_t.不要使用 uint32_t 等无符号整型, 除非你是在表示一个位组而不是一个数值, 或是你需要定义二进制补码溢出. 尤其是不要为了指出数值永不会为负, 而使用无符号类型. 相反, 你应该使用断言来保护数据.

关于无符号整数:
有些人, 包括一些教科书作者, 推荐使用无符号类型表示非负数. 这种做法试图达到自我文档化. 但是, 在 C 语言中, 这一优点被由其导致的 bug 所淹没. 看看下面的例子:

for (unsigned int i = foo.Length()-1; i >= 0; --i) ...

上述循环永远不会退出! 有时 gcc 会发现该 bug 并报警, 但大部分情况下都不会. 类似的 bug 还会出现在比较有符合变量和无符号变量时. 主要是 C 的类型提升机制会致使无符号类型的行为出乎你的意料.因此, 使用断言来指出变量为非负数, 而不是使用无符号型!

5.13. 64 位下的可移植性

Tip
代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记:

•对于某些类型, printf() 的指示符在 32 位和 64 位系统上可移植性不是很好. C99 标准定义了一些可移植的格式化指示符. 不幸的是, MSVC 7.1 并非全部支持, 而且标准中也有所遗漏, 所以有时我们不得不自己定义一个丑陋的版本 (头文件 inttypes.h 仿标准风格):

// printf macros for size_t, in the style of inttypes.h
#ifdef _LP64
#define __PRIS_PREFIX "z"
#else
#define __PRIS_PREFIX
#endif

// Use these macros after a % in a printf format string
// to get correct 32/64 bit behavior, like this:
// size_t size = records.size();
// printf("%"PRIuS"\n", size);
#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS __PRIS_PREFIX "o"

这里写图片描述

•注意 PRI* 宏会被编译器扩展为独立字符串. 因此如果使用非常量的格式化字符串, 需要将宏的值而不是宏名插入格式中. 使用 PRI* 宏同样可以在 % 后包含长度指示符. 例如, printf(“x = %30”PRIuS”\n”, x) 在 32 位 Linux 上将被展开为 printf(“x = %30” “u” “\n”, x), 编译器当成 printf(“x = %30u\n”, x) 处理 (yospaly 注: 这在 MSVC 6.0 上行不通, VC 6 编译器不会自动把引号间隔的多个字符串连接一个长字符串).

•记住 sizeof(void *) != sizeof(int). 如果需要一个指针大小的整数要用 intptr_t.

•你要非常小心的对待结构体对齐, 尤其是要持久化到磁盘上的结构体 (yospaly 注: 持久化 - 将数据按字节流顺序保存在磁盘文件或数据库中). 在 64 位系统中, 任何含有 int64_t/uint64_t 成员的类/结构体, 缺省都以 8 字节在结尾对齐. 如果 32 位和 64 位代码要共用持久化的结构体, 需要确保两种体系结构下的结构体对齐一致. 大多数编译器都允许调整结构体对齐. gcc 中可使用 attribute((packed)). MSVC 则提供了 #pragma pack() 和 __declspec(align()) (YuleFox 注, 解决方案的项目属性里也可以直接设置).

•创建 64 位常量时使用 LL 或 ULL 作为后缀, 如:

int64_t my_value = 0×123456789LL;
uint64_t my_mask = 3ULL << 48;

•如果你确实需要 32 位和 64 位系统具有不同代码, 可以使用 #ifdef _LP64 指令来切分 32/64 位代码. (尽量不要这么做, 如果非用不可, 尽量使修改局部化)

5.14. 预处理宏

Tip
使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.

宏意味着你和编译器看到的代码是不同的. 这可能会导致异常行为, 尤其因为宏具有全局作用域.

值得庆幸的是, C++ 中, 宏不像在 C 中那么必不可少. 以往用宏展开性能关键的代码, 现在可以用内联函数替代. 用宏表示常量可被 const 变量代替. 用宏 “缩写” 长变量名可被引用代替. 用宏进行条件编译… 这个, 千万别这么做, 会令测试更加痛苦 (#define 防止头文件重包含当然是个特例).

宏可以做一些其他技术无法实现的事情, 在一些代码库 (尤其是底层库中) 可以看到宏的某些特性 (如用 # 字符串化, 用 ## 连接等等). 但在使用前, 仔细考虑一下能不能不使用宏达到同样的目的.

下面给出的用法模式可以避免使用宏带来的问题; 如果你要宏, 尽可能遵守:
•不要在 .h 文件中定义宏.
•在马上要使用时才进行 #define, 使用后要立即 #undef.
•不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
•不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.

5.15. 0 和 NULL

Tip
整数用 0, 实数用 0.0, 指针用 NULL, 字符 (串) 用 '\0'.

整数用 0, 实数用 0.0, 这一点是毫无争议的.

对于指针 (地址值), 到底是用 0 还是 NULL, Bjarne Stroustrup 建议使用最原始的 0. 我们建议使用看上去像是指针的 NULL, 事实上一些 C++ 编译器 (如 gcc 4.1.0) 对 NULL 进行了特殊的定义, 可以给出有用的警告信息, 尤其是 sizeof(NULL) 和 sizeof(0) 不相等的情况.

字符 (串) 用 ‘\0’, 不仅类型正确而且可读性好.

5.16. sizeof

Tip
尽可能用 sizeof(varname) 代替 sizeof(type).

使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新. 某些情况下 sizeof(type) 或许有意义, 但还是要尽量避免, 因为它会导致变量类型改变后不能同步.

Struct data;
Struct data; memset(&data, 0, sizeof(data));

Warning

memset(&data, 0, sizeof(Struct));

5.17. Boost 库

Tip
只使用 Boost 中被认可的库.

定义:
Boost 库集 是一个广受欢迎, 经过同行鉴定, 免费开源的 C++ 库集.

优点:
Boost代码质量普遍较高, 可移植性好, 填补了 C++ 标准库很多空白, 如型别的特性, 更完善的绑定器, 更好的智能指针, 同时还提供了 TR1 (标准库扩展) 的实现.

缺点:
某些 Boost 库提倡的编程实践可读性差, 比如元编程和其他高级模板技术, 以及过度 “函数化” 的编程风格.

结论:
为了向阅读和维护代码的人员提供更好的可读性, 我们只允许使用 Boost 一部分经认可的特性子集. 目前允许使用以下库:
•Compressed Pair : boost/compressed_pair.hpp
•Pointer Container : boost/ptr_container (序列化除外)
•Array : boost/array.hpp
•The Boost Graph Library (BGL) : boost/graph (序列化除外)
•Property Map : boost/property_map.hpp
•Iterator中处理迭代器定义的部分 : boost/iterator/iterator_adaptor.hpp, boost/iterator/iterator_facade.hpp, 以及 boost/function_output_iterator.hpp
我们正在积极考虑增加其它 Boost 特性, 所以列表中的规则将不断变化.

6.命名约定
最重要的一致性规则是命名管理. 命名风格快速获知名字代表是什么东东: 类型? 变量? 函数? 常量? 宏 … ? 甚至不需要去查找类型声明. 我们大脑中的模式匹配引擎可以非常可靠的处理这些命名规则.命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重, 所以不管你怎么想, 规则总归是规则.

6.1. 通用命名规则

Tip
函数命名, 变量命名, 文件命名应具备描述性; 不要过度缩写. 类型和变量应该是名词, 函数名可以用 “命令性” 动词.

如何命名:
尽可能给出描述性的名称. 不要节约行空间, 让别人很快理解你的代码更重要. 好的命名风格:

int num_errors;                  // Good.
int num_completed_connections;   // Good.

糟糕的命名使用含糊的缩写或随意的字符:

int n;                           // Bad - meaningless.
int nerr;                        // Bad - ambiguous abbreviation.
int n_comp_conns;                // Bad - ambiguous abbreviation.

类型和变量名一般为名词: 如 FileOpener, num_errors.
函数名通常是指令性的 (确切的说它们应该是命令), 如 OpenFile(), set_num_errors(). 取值函数是个特例 (在 函数命名 处详细阐述), 函数名和它要取值的变量同名.

缩写:
除非该缩写在其它地方都非常普遍, 否则不要使用. 例如:

// Good
// These show proper names with no abbreviations.
int num_dns_connections;  // 大部分人都知道 "DNS" 是啥意思.
int price_count_reader;   // OK, price count. 有意义.

Warning

// Bad!
// Abbreviations can be confusing or ambiguous outside a small group.
int wgc_connections;  // Only your group knows what this stands for.
int pc_reader;        // Lots of things can be abbreviated "pc".

永远不要用省略字母的缩写:

int error_count;  // Good.
int error_cnt;    // Bad.

6.2. 文件命名

Tip
文件名要全部小写, 可以包含下划线 (_) 或连字符 (-). 按项目约定来.

可接受的文件命名:

my_useful_class.cc
my-useful-class.cc
myusefulclass.cc

C++ 文件要以 .cc 结尾, 头文件以 .h 结尾.

不要使用已经存在于 /usr/include 下的文件名 (yospaly 注: 即编译器搜索系统头文件的路径), 如 db.h.

通常应尽量让文件名更加明确. http_server_logs.h 就比 logs.h 要好. 定义类时文件名一般成对出现, 如 foo_bar.h 和 foo_bar.cc, 对应于类 FooBar.

内联函数必须放在 .h 文件中. 如果内联函数比较短, 就直接放在 .h 中. 如果代码比较长, 可以放到以 -inl.h 结尾的文件中. 对于包含大量内联代码的类, 可以使用三个文件:

url_table.h      // The class declaration.
url_table.cc     // The class definition.
url_table-inl.h  // Inline functions that include lots of code.

6.3. 类型命名

Tip
类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.

所有类型命名 —— 类, 结构体, 类型定义 (typedef), 枚举 —— 均使用相同约定. 例如:

// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...

// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// enums
enum UrlTableErrors { ...

6.4. 变量命名

Tip
变量名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 如:
my_exciting_local_variable
my_exciting_member_variable_

普通变量命名:
举例:

string table_name;  // OK - uses underscore.
string tablename;   // OK - all lowercase.

Warning

string tableName;   // Bad - mixed case.

结构体变量:
结构体的数据成员可以和普通变量一样, 不用像类那样接下划线:

struct UrlTableProperties {
    string name;
    int num_entries;
}

全局变量:
对全局变量没有特别要求, 少用就好, 但如果你要用, 可以用 g_ 或其它标志作为前缀, 以便更好的区分局部变量.

6.5. 常量命名

Tip
在名称前加 k: kDaysInAWeek.

所有编译时常量, 无论是局部的, 全局的还是类中的, 和其他变量稍微区别一下. k 后接大写字母开头的单词:
const int kDaysInAWeek = 7;

6.6. 函数命名

Tip
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().

常规函数:
函数名的每个单词首字母大写, 没有下划线:

AddTableEntry()
DeleteUrl()

取值和设值函数:
取值和设值函数要与存取的变量名匹配. 这儿摘录一个类, num_entries_ 是该类的实例变量:

class MyClass {
    public:
        ...
        int num_entries() const { return num_entries_; }
        void set_num_entries(int num_entries) { num_entries_ = num_entries; }

    private:
        int num_entries_;
};

其它非常短小的内联函数名也可以用小写字母, 例如. 如果你在循环中调用这样的函数甚至都不用缓存其返回值, 小写命名就可以接受.

6.7. 名字空间命名

Tip
名字空间用小写字母命名, 并基于项目名称和目录结构: google_awesome_project.

6.8. 枚举命名

Tip
枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
Cenum UrlTableErrors {
    kOK = 0,
    kErrorOutOfMemory,
    kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
    OK = 0,
    OUT_OF_MEMORY = 1,
    MALFORMED_INPUT = 2,
};

2009 年 1 月之前, 我们一直建议采用 宏 的方式命名枚举值. 由于枚举值和宏之间的命名冲突, 直接导致了很多问题. 由此, 这里改为优先选择常量风格的命名方式. 新代码应该尽可能优先使用常量风格. 但是老代码没必要切换到常量风格, 除非宏风格确实会产生编译期问题.

6.9. 宏命名

Tip
你并不打算 使用宏, 对吧? 如果你一定要用, 像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.

参考 预处理宏 ; 通常 不应该 使用宏. 如果不得不用, 其命名像枚举命名一样全部大写, 使用下划线:

#define ROUND(x) ...
#define PI_ROUNDED 3.0

6.10. 命名规则的特例

Tip
如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略.

bigopen():
函数名, 参照 open() 的形式
uint:
typedef
bigpos:
struct 或 class, 参照 pos 的形式
sparse_hash_map:
STL 相似实体; 参照 STL 命名约定
LONGLONG_MAX:
常量, 如同 INT_MAX

7. 注释
注释虽然写起来很痛苦, 但对保证代码可读性至关重要. 下面的规则描述了如何注释以及在哪儿注释. 当然也要记住: 注释固然很重要, 但最好的代码本身应该是自文档化. 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.
你写的注释是给代码读者看的: 下一个需要理解你的代码的人. 慷慨些吧, 下一个人可能就是你!

7.1. 注释风格

Tip
使用 // 或 /* */, 统一就好.
// 或 /* */ 都可以; 但 // 更 常用. 要在如何注释及注释风格上确保统一.

7.2. 文件注释

Tip
在每一个文件开头加入版权公告, 然后是文件内容描述.

法律公告和作者信息:
每个文件都应该包含以下项, 依次是:
•版权声明 (比如, Copyright 2008 Google Inc.)
•许可证. 为项目选择合适的许可证版本 (比如, Apache 2.0, BSD, LGPL, GPL)
•作者: 标识文件的原始作者.

如果你对原始作者的文件做了重大修改, 将你的信息添加到作者信息里. 这样当其他人对该文件有疑问时可以知道该联系谁.

文件内容:
紧接着版权许可和作者信息之后, 每个文件都要用注释描述文件内容.
通常, .h 文件要对所声明的类的功能和用法作简单说明. .cc 文件通常包含了更多的实现细节或算法技巧讨论, 如果你感觉这些实现细节或算法技巧讨论对于理解 .h 文件有帮助, 可以该注释挪到 .h, 并在 .cc 中指出文档在 .h.不要简单的在 .h 和 .cc 间复制注释. 这种偏离了注释的实际意义.

7.3. 类注释

Tip
每个类的定义都要附带一份注释, 描述类的功能和用法.
// Iterates over the contents of a GargantuanTable.  Sample usage:
//    GargantuanTable_Iterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTable_Iterator {
    ...
};

如果你觉得已经在文件顶部详细描述了该类, 想直接简单的来上一句 “完整描述见文件顶部” 也不打紧, 但务必确保有这类注释.
如果类有任何同步前提, 文档说明之. 如果该类的实例可被多线程访问, 要特别注意文档说明多线程环境下相关的规则和常量使用.

7.4. 函数注释

Tip
函数声明处注释描述函数功能; 定义处描述函数实现.

函数声明:
注释位于声明之前, 对函数功能及用法进行描述. 注释使用叙述式 (“Opens the file”) 而非指令式 (“Open the file”); 注释只是为了描述函数, 而不是命令函数做什么. 通常, 注释不会描述函数如何工作. 那是函数定义部分的事情.

函数声明处注释的内容:
•函数的输入输出.
•对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
•如果函数分配了空间, 需要由调用者释放.
•参数是否可以为 NULL.
•是否存在函数使用上的性能隐患.
•如果函数是可重入的, 其同步前提是什么?

举例如下:

// Returns an iterator for this table.  It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
//    Iterator* iter = table->NewIterator();
//    iter->Seek("");
//    return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;

但也要避免罗罗嗦嗦, 或做些显而易见的说明. 下面的注释就没有必要加上 “returns false otherwise”, 因为已经暗含其中了:

// Returns true if the table cannot hold any more entries.
bool IsTableFull();

注释构造/析构函数时, 切记读代码的人知道构造/析构函数是干啥的, 所以 “destroys this object” 这样的注释是没有意义的. 注明构造函数对参数做了什么 (例如, 是否取得指针所有权) 以及析构函数清理了什么. 如果都是些无关紧要的内容, 直接省掉注释. 析构函数前没有注释是很正常的.

函数定义:
每个函数定义时要用注释说明函数功能和实现要点. 比如说说你用的编程技巧, 实现的大致步骤, 或解释如此实现的理由, 为什么前半部分要加锁而后半部分不需要.不要从 .h 文件或其他地方的函数声明处直接复制注释. 简要重述函数功能是可以的, 但注释重点要放在如何实现上.

7.5. 变量注释

Tip
通常变量名本身足以很好说明变量用途. 某些情况下, 也需要额外的注释说明.

类数据成员:
每个类数据成员 (也叫实例变量或成员变量) 都应该用注释说明用途. 如果变量可以接受 NULL 或 -1 等警戒值, 须加以说明. 比如:

private:
    // Keeps track of the total number of entries in the table.
    // Used to ensure we do not go over the limit. -1 means
    // that we don't yet know how many entries the table has.
    int num_total_entries_;

全局变量:
和数据成员一样, 所有全局变量也要注释说明含义及用途. 比如:

// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

7.6. 实现注释

Tip
对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.

代码前注释:
巧妙或复杂的代码段前要加注释. 比如:

// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
    x = (x << 8) + (*result)[i];
    (*result)[i] = x >> 1;
    x &= 1;
}

行注释:
比较隐晦的地方要在行尾加入注释. 在行尾空两格进行注释. 比如:

// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
    return;  // Error already logged.

注意, 这里用了两段注释分别描述这段代码的作用, 和提示函数返回时错误已经被记入日志.
如果你需要连续进行多行注释, 可以使之对齐获得更好的可读性:

DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // Comment here so there are two spaces between
                                // the code and the comment.
{ // One space before comment when opening a new scope is allowed,
  // thus the comment lines up with the following comments and code.
  DoSomethingElse();  // Two spaces before line comments normally.
}

NULL, true/false, 1, 2, 3…:
向函数传入 NULL, 布尔值或整数时, 要注释说明含义, 或使用常量让代码望文知意. 例如, 对比:
Warning

bool success = CalculateSomething(interesting_value,
                                  10,
                                  false,
                                  NULL);  // What are these arguments??

bool success = CalculateSomething(interesting_value,
                          10,     // Default base value.
                          false,  // Not the first time we're calling this.
                          NULL);  // No callback.

或使用常量或描述性变量:

const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
                                  kDefaultBaseValue,
                                  kFirstTimeCalling,
                                  null_callback);

不允许:
注意 永远不要 用自然语言翻译代码作为注释. 要假设读代码的人 C++ 水平比你高, 即便他/她可能不知道你的用意:
Warning

// 现在, 检查 b 数组并确保 i 是否存在,
// 下一个元素是 i+1.
...        // 天哪. 令人崩溃的注释.

7.7. 标点, 拼写和语法

Tip
注意标点, 拼写和语法; 写的好的注释比差的要易读的多.

注释的通常写法是包含正确大小写和结尾句号的完整语句. 短一点的注释 (如代码行尾注释) 可以随意点, 依然要注意风格的一致性. 完整的语句可读性更好, 也可以说明该注释是完整的, 而不是一些不成熟的想法.
虽然被别人指出该用分号时却用了逗号多少有些尴尬, 但清晰易读的代码还是很重要的. 正确的标点, 拼写和语法对此会有所帮助.

7.8. TODO 注释

Tip
对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释.

TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的大名, 邮件地址, 或其它身份标识. 冒号是可选的. 主要目的是让添加注释的人 (也是可以请求提供更多细节的人) 可根据规范的 TODO 格式进行查找. 添加 TODO 注释并不意味着你要自己来修正.

// TODO([email protected]): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

如果加 TODO 是为了在 “将来某一天做某事”, 可以附上一个非常明确的时间 “Fix by November 2005”), 或者一个明确的事项 (“Remove this code when all clients can handle XML responses.”).

笔记
1.关于注释风格,很多 C++ 的 coders 更喜欢行注释, C coders 或许对块注释依然情有独钟, 或者在文件头大段大段的注释时使用块注释;

2.文件注释可以炫耀你的成就, 也是为了捅了篓子别人可以找你;

3.注释要言简意赅, 不要拖沓冗余, 复杂的东西简单化和简单的东西复杂化都是要被鄙视的;

4.对于 Chinese coders 来说, 用英文注释还是用中文注释, it is a problem, 但不管怎样, 注释是为了让别人看懂, 难道是为了炫耀编程语言之外的你的母语或外语水平吗;

5.注释不要太乱, 适当的缩进才会让人乐意看. 但也没有必要规定注释从第几列开始 (我自己写代码的时候总喜欢这样), UNIX/LINUX 下还可以约定是使用 tab 还是 space, 个人倾向于 space;

6.TODO 很不错, 有时候, 注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索, 就知道还有哪些活要干, 日志都省了.

8. 格式
代码风格和格式确实比较随意, 但一个项目中所有人遵循同一风格是非常容易的. 个体未必同意下述每一处格式规则, 但整个项目服从统一的编程风格是很重要的, 只有这样才能让所有人能很轻松的阅读和理解代码.

8.1. 行长度

Tip
每一行代码字符数不超过 80.

我们也认识到这条规则是有争议的, 但很多已有代码都已经遵照这一规则, 我们感觉一致性更重要.

优点:
提倡该原则的人主张强迫他们调整编辑器窗口大小很野蛮. 很多人同时并排开几个代码窗口, 根本没有多余空间拉伸窗口. 大家都把窗口最大尺寸加以限定, 并且 80 列宽是传统标准. 为什么要改变呢?

缺点:
反对该原则的人则认为更宽的代码行更易阅读. 80 列的限制是上个世纪 60 年代的大型机的古板缺陷; 现代设备具有更宽的显示屏, 很轻松的可以显示更多代码.

结论:
80 个字符是最大值.

特例:
•如果一行注释包含了超过 80 字符的命令或 URL, 出于复制粘贴的方便允许该行超过 80 字符.
•包含长路径的 #include 语句可以超出80列. 但应该尽量避免.
头文件保护 可以无视该原则.

8.2. 非 ASCII 字符

Tip
尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.

即使是英文, 也不应将用户界面的文本硬编码到源代码中, 因此非 ASCII 字符要少用. 特殊情况下可以适当包含此类字符. 如, 代码分析外部数据文件时, 可以适当硬编码数据文件中作为分隔符的非 ASCII 字符串; 更常见的是 (不需要本地化的) 单元测试代码可能包含非 ASCII 字符串. 此类情况下, 应使用 UTF-8 编码, 因为很多工具都可以理解和处理 UTF-8 编码. 十六进制编码也可以, 能增强可读性的情况下尤其鼓励 —— 比如 “\xEF\xBB\xBF” 在 Unicode 中是 零宽度 无间断 的间隔符号, 如果不用十六进制直接放在 UTF-8 格式的源文件中, 是看不到的. (注: “\xEF\xBB\xBF” 通常用作 UTF-8 with BOM 编码标记)

8.3. 空格还是制表位

Tip
只使用空格, 每次缩进 2 个空格.

我们使用空格缩进. 不要在代码中使用制符表. 你应该设置编辑器将制符表转为空格.

8.4. 函数声明与定义

Tip
返回类型和函数名在同一行, 参数也尽量放在同一行.

函数看上去像这样:

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 个空格的缩进;
如果函数声明成 const, 关键字 const 应与最后一个参数位于同一行:

// Everything in this function signature fits on a single line
ReturnType FunctionName(Type par) const {
  ...
}

// This function signature requires multiple lines, but
// the const keyword is on the line with the last parameter.
ReturnType ReallyLongFunctionName(Type par1,
                                  Type par2) const {
  ...
}

如果有些参数没有用到, 在函数定义处将参数名注释起来:

// Always have named parameters in interfaces.
class Shape {
 public:
  virtual void Rotate(double radians) = 0;
}

// Always have named parameters in the declaration.
class Circle : public Shape {
 public:
  virtual void Rotate(double radians);
}

// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}

Warning

// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}

8.5. 函数调用

Tip
尽量放在同一行, 否则, 将实参封装在圆括号中.

函数调用遵循如下形式:

bool retval = DoSomething(argument1, argument2, argument3);

如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:

bool retval = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);

如果函数参数很多, 出于可读性的考虑可以在每行只放一个参数:

bool retval = DoSomething(argument1,
                          argument2,
                          argument3,
                          argument4);

如果函数名非常长, 以至于超过 行最大长度, 可以将所有参数独立成行:

if (...) {
  ...
  ...
  if (...) {
    DoSomethingThatRequiresALongFunctionName(
        very_long_argument1,  // 4 space indent
        argument2,
        argument3,
        argument4);
  }

8.6. 条件语句

Tip
倾向于不在圆括号内使用空格. 关键字 else 另起一行.

对基本条件语句有两种可以接受的格式. 一种在圆括号和条件之间有空格, 另一种没有.

最常见的是没有空格的格式. 哪种都可以, 但 保持一致性. 如果你是在修改一个文件, 参考当前已有格式. 如果是写新的代码, 参考目录下或项目中其它文件. 还在徘徊的话, 就不要加空格了.

if (condition) {  // no spaces inside parentheses
  ...  // 2 space indent.
} else {  // The else goes on the same line as the closing brace.
  ...
}

如果你更喜欢在圆括号内部加空格:

if ( condition ) {  // spaces inside parentheses - rare
  ...  // 2 space indent.
} else {  // The else goes on the same line as the closing brace.
  ...
}

注意所有情况下 if 和左圆括号间都有个空格. 右圆括号和左大括号之间也要有个空格:
Warning

if(condition)     // Bad - space missing after IF.
if (condition){   // Bad - space missing before {.
if(condition){    // Doubly bad.
if (condition) {  // Good - proper space after IF and before {.

如果能增强可读性, 简短的条件语句允许写在同一行. 只有当语句简单并且没有使用 else 子句时使用:

if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();

如果语句有 else 分支则不允许:
Warning

// Not allowed - IF statement on one line when there is an ELSE clause
if (x) DoThis();
else DoThat();

通常, 单行语句不需要使用大括号, 如果你喜欢用也没问题; 复杂的条件或循环语句用大括号可读性会更好. 也有一些项目要求 if 必须总是使用大括号:

if (condition)
  DoSomething();  // 2 space indent.

if (condition) {
  DoSomething();  // 2 space indent.
}

但如果语句中某个 if-else 分支使用了大括号的话, 其它分支也必须使用:
Warning

// Not allowed - curly on IF but not ELSE
if (condition) {
    foo;
} else
    bar;

// Not allowed - curly on ELSE but not IF
if (condition)
    foo;
else {
    bar;
}
// Curly braces around both IF and ELSE required because
// one of the clauses used braces.
if (condition) {
  foo;
} else {
  bar;
}

8.7. 循环和开关选择语句

Tip
switch 语句可以使用大括号分段. 空循环体应使用 {} 或 continue.

switch 语句中的 case 块可以使用大括号也可以不用, 取决于你的个人喜好. 如果用的话, 要按照下文所述的方法.
如果有不满足 case 条件的枚举值, switch 应该总是包含一个 default 匹配 (如果有输入值没有 case 去处理, 编译器将报警). 如果 default 应该永远执行不到, 简单的加条 assert:

switch (var) {
  case 0: {  // 2 space indent
    ...      // 4 space indent
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}

空循环体应使用 {} 或 continue, 而不是一个简单的分号.

while (condition) {
  // Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {}  // Good - empty body.
while (condition) continue;  // Good - continue indicates no logic.

Warning

while (condition);  // Bad - looks like part of do/while loop.

8.8. 指针和引用表达式

Tip
句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格.

下面是指针和引用表达式的正确使用范例:

x = *p;
p = &x;
x = r.y;
x = r->y;

注意:
•在访问成员时, 句点或箭头前后没有空格.
•指针操作符 * 或 & 后没有空格.
在声明指针变量或参数时, 星号与类型或变量名紧挨都可以:

// These are fine, space preceding.
char *c;
const string &str;

// These are fine, space following.
char* c;    // but remember to do "char* c, *d, *e, ...;"!
const string& str;

Warning

char * c;  // Bad - spaces on both sides of *
const string & str;  // Bad - spaces on both sides of &

在单个文件内要保持风格一致, 所以, 如果是修改现有文件, 要遵照该文件的风格.

8.9. 布尔表达式

Tip
如果一个布尔表达式超过 标准行宽, 断行方式要统一一下.

下例中, 逻辑与 (&&) 操作符总位于行尾:

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another & last_one) {
  ...
}

注意, 上例的逻辑与 (&&) 操作符均位于行尾. 可以考虑额外插入圆括号, 合理使用的话对增强可读性是很有帮助的.

8.10. 函数返回值

Tip
return 表达式中不要用圆括号包围.

函数返回时不要使用圆括号:

return x;  // not return(x);

8.11. 变量及数组初始化

Tip
用 = 或 () 均可.

在二者中做出选择; 下面的方式都是正确的:

int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";

8.12. 预处理指令

Tip
预处理指令不要缩进, 从行首开始.

即使预处理指令位于缩进代码块中, 指令也应从行首开始.

// Good - directives at beginning of line
  if (lopsided_score) {
#if DISASTER_PENDING      // Correct -- Starts at beginning of line
    DropEverything();
#endif
    BackToNormal();
  }

Warning

// 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();
  }

8.13. 类格式

Tip
访问控制块的声明依次序是 public:, protected:, private:, 每次缩进 1 个空格.

类声明 (对类注释不了解的话, 参考 类注释) 的基本格式如下:

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_;
  DISALLOW_COPY_AND_ASSIGN(MyClass);
};

注意事项:
•所有基类名应在 80 列限制下尽量与子类名放在同一行.
•关键词 public:, protected:, private: 要缩进 1 个空格.
•除第一个关键词 (一般是 public) 外, 其他关键词前要空一行. 如果类比较小的话也可以不空.
•这些关键词后不要保留空行.
•public 放在最前面, 然后是 protected, 最后是 private.
•关于声明顺序的规则请参考 声明顺序 一节.

8.14. 初始化列表

Tip
构造函数初始化列表放在同一行或按四格缩进并排几行.

下面两种初始化列表方式都可以接受:

// When it all fits on one line:
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {

// When it requires multiple lines, indent 4 spaces, putting the colon on
// the first initializer line:
MyClass::MyClass(int var)
    : some_var_(var),             // 4 space indent
      some_other_var_(var + 1) {  // lined up
  ...
  DoSomething();
  ...
}

8.15. 名字空间格式化

Tip
名字空间内容不缩进.

名字空间 不要增加额外的缩进层次, 例如:

namespace {

void foo() {  // Correct.  No extra indentation within namespace.
  ...
}

}  // namespace

不要缩进名字空间:
Warning

namespace {

  // Wrong.  Indented when it should not be.
  void foo() {
    ...
  }

}  // namespace

8.16. 水平留白

Tip
水平留白的使用因地制宜. 永远不要在行尾添加没意义的留白.

常规:

void f(bool b) {  // Open braces should always have a space before them.
  ...
int i = 0;  // Semicolons usually have no space before them.
int x[] = { 0 };  // Spaces inside braces for array initialization are
int x[] = {0};    // optional.  If you use them, put them on both sides!
// 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.
  ...

添加冗余的留白会给其他人编辑时造成额外负担. 因此, 行尾不要留空格. 如果确定一行代码已经修改完毕, 将多余的空格去掉; 或者在专门清理空格时去掉(确信没有其他人在处理). (注: 现在大部分代码编辑器稍加设置后, 都支持自动删除行首/行尾空格, 如果不支持, 考虑换一款编辑器或 IDE)

循环和条件语句:

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) {
switch ( i ) {    // Loops and conditions may have spaces inside
if ( test ) {     // parentheses, but this is rare.  Be consistent.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) {  // For loops always have a space after the
  ...                   // semicolon, and may have a space before the
                        // semicolon.
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.

操作符:

x = 0;              // Assignment operators always have spaces around
                    // them.
x = -5;             // No spaces separating unary operators and their
++x;                // arguments.
if (x && !y)
  ...
v = w * x + y / z;  // Binary operators usually have spaces around them,
v = w*x + y/z;      // but it's okay to remove spaces around factors.
v = w * (x + z);    // Parentheses should have no spaces inside them.

模板和转换:

vector<string> x;           // No spaces inside the angle
y = static_cast<char*>(x);  // brackets (< and >), before
                            // <, or between >( in a cast.
vector<char *> x;           // Spaces between type and pointer are
                            // okay, but be consistent.
set<list<string> > x;       // C++ requires a space in > >.
set< list<string> > x;      // You may optionally make use
                            // symmetric spacing in < <.

8.17. 垂直留白

Tip
垂直留白越少越好.

这不仅仅是规则而是原则问题了: 不在万不得已, 不要使用空行. 尤其是: 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行.

基本原则是: 同一屏可以显示的代码越多, 越容易理解程序的控制流. 当然, 过于密集的代码块和过于疏松的代码块同样难看, 取决于你的判断. 但通常是垂直留白越少越好.

Warning
函数首尾不要有空行
void Function() {

  // Unnecessary blank lines before and after

}
Warning
代码块首尾不要有空行
while (condition) {
  // Unnecessary blank line after

}
if (condition) {

  // Unnecessary blank line before
}

if-else 块之间空一行是可以接受的:

if (condition) {
  // Some lines of code too small to move to another function,
  // followed by a blank line.

} else {
  // Another block of code
}

笔记
1.对于代码格式, 因人, 系统而异各有优缺点, 但同一个项目中遵循同一标准还是有必要的;

2.行宽原则上不超过 80 列, 把 22 寸的显示屏都占完, 怎么也说不过去;

3.尽量不使用非 ASCII 字符, 如果使用的话, 参考 UTF-8 格式 (尤其是 UNIX/Linux 下, Windows 下可以考虑宽字符), 尽量不将字符串常量耦合到代码中, 比如独立出资源文件, 这不仅仅是风格问题了;

4.UNIX/Linux 下无条件使用空格, MSVC 的话使用 Tab 也无可厚非;

5.函数参数, 逻辑条件, 初始化列表: 要么所有参数和函数名放在同一行, 要么所有参数并排分行;

6.除函数定义的左大括号可以置于行首外, 包括函数/类/结构体/枚举声明, 各种语句的左大括号置于行尾, 所有右大括号独立成行;

7../-> 操作符前后不留空格, */& 不要前后都留, 一个就可, 靠左靠右依各人喜好;

8.预处理指令/命名空间不使用额外缩进, 类/结构体/枚举/函数/语句使用缩进;

9.初始化用 = 还是 () 依个人喜好, 统一就好;

10.return 不要加 ();

11.水平/垂直留白不要滥用, 怎么易读怎么来;

12.关于 UNIX/Linux 风格为什么要把左大括号置于行尾 (.cc 文件的函数实现处, 左大括号位于行首), 我的理解是代码看上去比较简约, 想想行首除了函数体被一对大括号封在一起之外, 只有右大括号的代码看上去确实也舒服; Windows 风格将左大括号置于行首的优点是匹配情况一目了然.

9.规则特例
前面说明的编程习惯基本都是强制性的. 但所有优秀的规则都允许例外, 这里就是探讨这些特例.

9.1. 现有不合规范的代码

Tip
对于现有不符合既定编程风格的代码可以网开一面.

当你修改使用其他风格的代码时, 为了与代码原有风格保持一致可以不使用本指南约定. 如果不放心可以与代码原作者或现在的负责人员商讨, 记住, 一致性 包括原有的一致性.

9.2. Windows 代码

Tip
Windows 程序员有自己的编程习惯, 主要源于 Windows 头文件和其它 Microsoft 代码. 我们希望任何人都可以顺利读懂你的代码, 所以针对所有平台的 C++ 编程只给出一个单独的指南.

如果你习惯使用 Windows 编码风格, 这儿有必要重申一下某些你可能会忘记的指南:
•不要使用匈牙利命名法 (比如把整型变量命名成 iNum). 使用 Google 命名约定, 包括对源文件使用 .cc 扩展名.

•Windows 定义了很多原生类型的同义词 (YuleFox 注: 这一点, 我也很反感), 如 DWORD, HANDLE 等等. 在调用 Windows API 时这是完全可以接受甚至鼓励的. 但还是尽量使用原有的 C++ 类型, 例如, 使用 const TCHAR * 而不是 LPCTSTR.

•使用 Microsoft Visual C++ 进行编译时, 将警告级别设置为 3 或更高, 并将所有 warnings 当作 errors 处理.

•不要使用 #pragma once; 而应该使用 Google 的头文件保护规则. 头文件保护的路径应该相对于项目根目录 (yospaly 注: 如 #ifndef SRC_DIR_BAR_H_, 参考 #define 保护 一节).

•除非万不得已, 不要使用任何非标准的扩展, 如 #pragma 和 __declspec. 允许使用 __declspec(dllimport) 和 __declspec(dllexport); 但你必须通过宏来使用, 比如 DLLIMPORT 和 DLLEXPORT, 这样其他人在分享使用这些代码时很容易就去掉这些扩展. 在 Windows 上, 只有很少的一些情况下, 我们可以偶尔违反规则.

•通常我们 禁止使用多重继承, 但在使用 COM 和 ATL/WTL 类时可以使用多重继承. 为了实现 COM 或 ATL/WTL 类/接口, 你可能不得不使用多重实现继承.

•虽然代码中不应该使用异常, 但是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中异常被广泛使用. 使用 ATL 时, 应定义 _ATL_NO_EXCEPTIONS 以禁用异常. 你要研究一下是否能够禁用 STL 的异常, 如果无法禁用, 启用编译器异常也可以. (注意这只是为了编译 STL, 自己代码里仍然不要含异常处理.)

•通常为了利用头文件预编译, 每个每个源文件的开头都会包含一个名为 StdAfx.h 或 precompile.h 的文件. 为了使代码方便与其他项目共享, 避免显式包含此文件 (precompile.cc), 使用 /FI 编译器选项以自动包含.

•资源头文件通常命名为 resource.h, 且只包含宏的, 不需要遵守本风格指南.

10. 结束语

Tip
运用常识和判断力, 并 保持一致.

编辑代码时, 花点时间看看项目中的其它代码, 并熟悉其风格. 如果其它代码中 if 语句使用空格, 那么你也要使用. 如果其中的注释用星号 (*) 围成一个盒子状, 你同样要这么做.

风格指南的重点在于提供一个通用的编程规范, 这样大家可以把精力集中在实现内容而不是表现形式上. 我们展示了全局的风格规范, 但局部风格也很重要, 如果你在一个文件中新加的代码和原有代码风格相去甚远, 这就破坏了文件本身的整体美观, 也影响阅读, 所以要尽量避免.

好了, 关于编码风格写的够多了; 代码本身才更有趣. 尽情享受吧!

发布了31 篇原创文章 · 获赞 48 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/mall_lucy/article/details/60869654
今日推荐