Google 开源项目风格指南 (中文版) 2017版

https://download.csdn.net/download/jcq521045349/10336158

Google 开源项目风格指南 (中文版) 2017版

https://www.it610.com/article/4231079.htm

linux消息队列msgrcv收不到消息的问题

2.2.2 #define 保护

Tip: 所有头文件都应该使用#define 来防止头文件被多重包含, 命名格式当是:
H .

为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径. 例如, 项目foo 中的头文件
foo/src/bar/baz.h 可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

2.2.3 前置声明

Tip: 尽可能地避免使用前置声明。使用#include 包含需要的头文件即可。

定义:
所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.
优点:
• 前置声明能够节省编译时间,多余的#include 会迫使编译器展开更多的文件,处理更多的输入。
• 前置声明能够节省不必要的重新编译的时间。#include 使代码因为头文件中无关的改动而被重新
编译多次。
缺点:
• 前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
• 前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者
变动其API. 例如扩大形参类型,加个自带默认参数的模板形参等等。
• 前置声明来自命名空间std:: 的symbol 时,其行为未定义。
• 很难判断什么时候该用前置声明,什么时候该用#include 。极端情况下,用前置声明
代替includes 甚至都会暗暗地改变代码的含义:

// b.h:
struct B {};
struct D : B {}
// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); } // calls f(B*)

结论:
• 尽量避免前置声明那些定义在其他项目中的实体.
• 函数:总是使用#include.
• 类模板:优先使用#include.

2.2.4 内联函数

Tip: 只有当函数只有10 行甚至更少时才将其定义为内联函数.

定义:
当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调
用.
优点:
只要内联的函数体较小, 内联该函数可以令目标代码更加高效. 对于存取函数以及其它函数体
比较短, 性能关键的函数, 鼓励使用内联.
缺点:
滥用内联将导致程序变得更慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小.
内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代
码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
结论:
一个较为合理的经验准则是, 不要内联超过10 行的函数. 谨慎对待析构函数, 析构函数往往
比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
另一个实用的经验准则: 内联那些包含循环或switch 语句的函数常常是得不偿失(除非在大
多数情况下, 这些循环或switch 语句从不被执行).
有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就
不会被正常内联. 通常, 递归函数不应该声明成内联函数.(YuleFox 注: 递归调用堆栈的展开
并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归
函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作
文档描述其行为, 比如精短的存取函数.

2.2.5 #include 的路径及顺序

Tip: 使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C 库, C++ 库, 其他库的
.h, 本项目内的.h.

#include "foo/public/fooserver.h" // 优先位置
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

2.2.6 译者(YuleFox) 笔记

  1. 避免多重包含是学编程时最基本的要求;
  2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;
  3. 内联函数的合理使用可提高代码执行效率;
  4. -inl.h 可提高代码可读性(一般用不到吧:D);
  5. 标准化函数参数顺序可以提高可读性和易维护性(对函数参数的堆栈空间有轻微影响, 我以前大多
    是相同类型放在一起);
  6. 包含文件的名称使用. 和… 虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理,
    包含文件的次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在“最需要编译” (对
    应源文件处:D) 的地方编译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在
    对应源文件的最前面, 这一点足以保证内部错误的及时发现了.

2.2.7 译者(acgtyrant)笔记

  1. 原来还真有项目用#includes 来插入文本,且其文件扩展名.inc 看上去也很科学。
  2. Google 已经不再提倡-inl.h 用法。
  3. 注意,前置声明的类是不完全类型(incomplete type),我们只能定义指向该类型的指针或引用,或
    者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。毕竟编译器不知道不完全类型
    的定义,我们不能创建其类的任何对象,也不能声明成类内部的数据成员。
  4. 类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是
    放到对应的.cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原
    则。
  5. 在#include 中插入空行以分割相关头文件, C 库, C++ 库, 其他库的.h 和本项目内的.h 是个好
    习惯。

2.8.1 7.1 通用命名规则

int price_count_reader; // 无缩写
int num_errors; // "num" 是一个常见的写法
int num_dns_connections; // 人人都知道"DNS" 是什么

注意, 一些特定的广为人知的缩写是允许的, 例如用i 表示迭代变量和用T 表示模板参数.
模板参数的命名应当遵循对应的分类: 类型模板参数应当遵循类型命名的规则, 而非类型模板应当遵
循变量命名的规则.

2.8.1 7.1 文件命名

总述
文件名要全部小写, 可以包含下划线() 或连字符(-), 依照项目的约定. 如果没有约定, 那么“” 更好

说明
可接受的文件命名示例:

• my_useful_class.cc
• my-useful-class.cc
• myusefulclass.cc
• myusefulclass_test.cc // _unittest 和_regtest 已弃用.

通常应尽量让文件名更加明确. http_server_logs.h 就比logs.h 要好. 定义类时文件名一般成对出现,
如foo_bar.h 和foo_bar.cc, 对应于类FooBar.
内联函数必须放在.h 文件中. 如果内联函数比较短, 就直接放在.h 中.

2.8.3 7.3. 类型命名

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

说明
所有类型命名——类, 结构体, 类型定义(typedef), 枚举, 类型模板参数——均使用相同约定, 即以大写
字母开始, 每个单词首字母均大写, 不包含下划线. 例如:

// 类和结构体
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// 类型定义
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// using 别名
using PropertiesMap = hash_map<UrlTableProperties *, string>;
// 枚举
enum UrlTableErrors { ...

2.8.4 7.4. 变量命名

总述
变量(包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结
构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.

普通变量命名

string table_name; // 好- 用下划线.
string tablename; // 好- 全小写.
string tableName; // 差- 混合大小写

类数据成员
不管是静态的还是非静态的, 类数据成员都可以和普通变量一样, 但要接下划线.

class TableInfo {
...
private:
string table_name_; // 好- 后加下划线.
string tablename_; // 好.
static Pool<TableInfo>* pool_; // 好.
};

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

struct UrlTableProperties {
string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};

2.8.5 7.5. 常量命名
总述
声明为constexpr 或const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以“k” 开头, 大小
写混合. 例如:

const int kDaysInAWeek = 7;

说明
所有具有静态存储类型的变量(例如静态变量或全局变量, 参见存储类型) 都应当以此方式命名. 对于其
他存储类型的变量, 如自动变量等, 这条规则是可选的. 如果不采用这条规则, 就按照一般的变量命名规
则.

2.8.6 7.7. 函数命名
总述
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(),
MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().
说明
一般来说, 函数名的每个单词首字母大写(即“驼峰变量名” 或“帕斯卡变量名”), 没有下划线. 对于首字母
缩写的单词, 更倾向于将它们视作一个单词进行首字母大写(例如, 写作StartRpc() 而非StartRPC()).

AddTableEntry()
DeleteUrl()
OpenFileOrDie()

2.8.7 7.7. 命名空间命名
总述
命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称. 要注意避免嵌套命名空间的名字之
间和常见的顶级命名空间的名字之间发生冲突.
顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字. 命名空间中的代码, 应
当存放于和命名空间的名字匹配的文件夹或其子文件夹中.
注意不使用缩写作为名称的规则同样适用于命名空间. 命名空间中的代码极少需要涉及命名空间的名称,
因此没有必要在命名空间中使用缩写.

2.8.8 7.8. 枚举命名
总述
枚举的命名应当和常量或宏一致: kEnumName 或是ENUM_NAME.
说明
单独的枚举值应该优先采用常量的命名方式. 但宏方式的命名也可以接受. 枚举名UrlTableErrors (以
及AlternateUrlTableErrors) 是类型, 所以要用大小写混合的方式.

enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};

2.8.9 7.9. 宏命名
总述
你并不打算使用宏, 对吧? 如果你一定要用, 像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
说明
参考预处理宏; 通常不应该使用宏. 如果不得不用, 其命名像枚举命名一样全部大写, 使用下划线:

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

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

2.9.3 8.3. 类注释
总述
每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显.

// Iterates over the contents of a GargantuanTable.
// Example:
// GargantuanTableIterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTableIterator {
...
};

2.9.4 8.4. 函数注释

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

函数声明
基本上每个函数声明处前都应当加上注释, 描述函数的功能和用途. 只有在函数的功能简单而明显时才能
省略这些注释(例如, 简单的取值和设值函数). 注释使用叙述式(“Opens the file”) 而非指令式(“Open
the file”); 注释只是为了描述函数, 而不是命令函数做什么. 通常, 注释不会描述函数如何工作. 那是函数
定义部分的事情.
函数声明处注释的内容:
• 函数的输入输出.
• 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
• 函数是否分配了必须由调用者释放的空间.
• 参数是否可以为空指针.
• 是否存在函数使用上的性能隐患.
• 如果函数是可重入的, 其同步前提是什么?

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

类数据成员
每个类数据成员(也叫实例变量或成员变量) 都应该用注释说明用途. 如果有非变量的参数(例如特殊值,
数据成员之间的关系, 生命周期等) 不能够用类型与变量名明确表达, 则应当加上注释. 然而, 如果变量类
型与变量名已经足以描述一个变量, 那么就不再需要加上注释.
特别地, 如果变量可以接受NULL 或-1 等警戒值, 须加以说明. 比如:

private:
// Used to bounds-check table accesses. -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;

2.9.6 8.6. 实现注释

总述
对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.
说明
代码前注释
巧妙或复杂的代码段前要加注释. 比如:

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

std::vector<string> list{
// Comments in braced lists describe the next element...
"First item",
// .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */

函数参数注释

// What are these arguments?
const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);
//对比

ProductOptions options;
options.set_precision_decimals(7);
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
CalculateProduct(values, options, /*completion_callback=*/nullptr);

2.9.8 8.8. TODO 注释
总述
对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用TODO 注释.
TODO 注释要使用全大写的字符串TODO, 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份
标识和与这一TODO 相关的issue. 主要目的是让添加注释的人(也是可以请求提供更多细节的人) 可根据
规范的TODO 格式进行查找. 添加TODO 注释并不意味着你要自己来修正, 因此当你加上带有姓名的TODO
时, 一般都是写上自己的名字.

2.10
1.
2. 行宽原则上不超过 80 列, 把 22 寸的显示屏都占完, 怎么也说不过去;
3.
4.
5. 函数参数, 逻辑条件, 初始化列表: 要么所有参数和函数名放在同一行, 要么所有参数并排分行;
6. 除函数定义的左大括号可以置于行首外, 包括函数/类/结构体/枚举声明, 各种语句的左大括号置于 行尾, 所有右大括号独立成行;
7. ./-> 操作符前后不留空格, */& 不要前后都留, 一个就可, 靠左靠右依各人喜好;
8. 预处理指令/命名空间不使用额外缩进, 类/结构体/枚举/函数/语句使用缩进;
9. 初始化用 = 还是 () 依个人喜好, 统一就好;
10. return 不要加 ();
11.
12. 关于 UNIX/Linux 风格为什么要把左大括号置于行尾 (.cc 文件的函数实现处, 左大括号位于行 首), 我的理解是代码看上去比较简约, 想想行首除了函数体被一对大括号封在一起之外, 只有右大 括号的代码看上去确实也舒服; Windows 风格将左大括号置于行首的优点是匹配情况一目了然.

2.11

  1. 80 行限制事实上有助于避免代码可读性失控, 比如超多重嵌套块, 超多重函数调用等等.
  2. Google 强调有一对 if-else 时, 不论有没有嵌套, 都要有大括号. Apple 正好 有栽过跟头 .
  3. 其实我主张指针/地址操作符与变量名紧邻, int* a, b vs int *a, b, 新手会误以为前者的 b 是 int * 变量, 但后者就不一样了, 高下立判.
  4. 在这风格指南里我才刚知道 C++ 原来还有所谓的 Alternative operator representations, 大概没人 用吧.
  5. 注意构造函数初始值列表(Constructer Initializer List)与列表初始化(Initializer List)是两码事, 我就差点混淆了它们的翻译.
  6. 事实上, 如果您熟悉英语本身的书写规则, 就会发现该风格指南在格式上的规定与英语语法相当一 脉相承. 比如普通标点符号和单词后面还有文本的话, 总会留一个空格; 特殊符号与单词之间就不 用留了, 比如 if (true) 中的圆括号与 true.
  7. 本风格指南没有明确规定 void 函数里要不要用 return 语句, 不过就 Google 开源项目 leveldb 并 没有写; 此外从 Is a blank return statement at the end of a function whos return type is void necessary? 来看, return; 比 return ; 更约定俗成(事实上 cpplint 会对后者报错, 指出分号前有 多余的空格), 且可用来提前跳出函数栈
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值