Google Style 学习


头文件

  • 头文件避免多重包含。#ifndef PROJ_PATH_FILE_H_
  • 能用前置声明就不要使用#include
  • 函数的参数顺序:输入参数,输出参数。

作用域

  • 不要使用using
  • 命名空间不需缩进。
  • .c中可以使用using.h中必须在函数、方法、类内部使用。
  • 非成员函数、静态成员函数、全局函数尽量放到命名空间中。
  • 局部变量声明的时候赋值。在离第一次使用尽可能近的地方声明。
  • 可在while循环中声明变量限制其作用域。
  • 注意有时需在循环外声明变量,比如类的对象,在循环内声明需要循环调用构造函数和析构函数。

  • 构造函数只进行一些非重要内容的初始化,因为如果操作失败会造成对象初始化失败,进入不确定状态。
    可能的话,使用Init()方法集中初始化有意义的数据。
  • 如果类有很多个变量,最好设置默认构造函数,否则编译器可能会生成一个很糟糕的构造函数。
  • 对单个参数的构造函数使用explicit关键字,防止默认强制类型转换。
  • 一般如果没有拷贝构造函数,编译器会自动声明拷贝构造函数,而且是public的。
    如果是单例模式需要禁止拷贝,则可以在private中声明但不定义拷贝构造函数,这样当试图使用它们时编译器将报错。用宏来描述:
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \\
            TypeName(const TypeName&); \\
            void operator=(const TypeName&)
         private:
        DISALLOW_COPY_AND_ASSIGN(Foo);
  • 当只有数据时使用struct,其他一概用classstruct只可以有构造函数,析构函数,Initialize()Reset()Validate()
  • 组合 > 实现继承 > 接口继承 > 私有继承,子类重载的虚函数也要声明virtual关键字。
  • 使用组合(composition)通常比继承更合理。如果需要类继承,使用public继承。
  • 尽量做到is-a继承,在has-a继承时尽量使用composition
  • 数据成员在任何情况下应该都只是私有的。
  • 当定义一个虚函数时,明确声明其为virtual,使得代码阅读者在检查基类的时候易于判断。
    如果类有虚函数,则析构函数最好也声明为virtual
  • 真正需要用到多重实现继承的情况少之又少。
    只在以下情况我们才允许多重继承:最多只有一个基类是非抽象类,其它基类都是以Interface为后缀的纯接口类。接口类类名以Interface为后缀。
  • 为降低复杂性,尽量不重载操作符。
  • 在类中使用特定的声明顺序:public:在 private:之前,成员函数在数据成员(变量)前。顺序:
  • typedefs和枚举
  • 常量->构造函数
  • 析构函数
  • 成员函数, 含静态成员函数
  • 数据成员, 含静态数据成员
  • 将所有数据成员声明为 private, 并根据需要提供相应的存取函数。存取函数一般内联在头文件中。
  • 倾向编写简短, 凝练的函数。如果函数超过 40 行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。

其他 C++ 特性

  • 所有按引用传递参数必须加上const。函数中非输出参数最好都加上const
  • 在任何可能的地方加上const
  • 如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用 AppendString()AppendInt()而不是Append()
  • 尽可能少的将函数的参数设置默认值。
  • 减少使用变长数组。
  • 允许合理的友元类和友元函数。
  • 类型转换时需要明确指明static_castconst_castreinterpret_castdynamic_cast
  • 只在打印日志的时候使用流。流最大的优势是在输出时不需要关心打印对象的类型。
    这是一个亮点,同时也是一个不足:你很容易用错类型,而编译器不会报警。
    使用流时容易造成的这类错误:
cout << this;   // Prints the address
 cout << *this;  // Prints the contents
  • 对于迭代器和其他模板对象使用前置的自增自减。
  • 使用宏时要非常谨慎,尽量以内联函数、枚举和常量代替之。
    不要在 .h 文件中定义宏。
    在马上要使用时才进行 #define。使用后要立即 #undef
  • 整数用 0,实数用0.0,指针用 NULL,字符(串)用'\\0'
  • 尽可能用sizeof(varname)代替sizeof(type),代码中变量类型改变后可以进行自动更新。

命名约定

  • 尽可能给出描述性的名称。不要节约行空间,让别人很快理解你的代码更重要。
 int num_errors;                  // Good.
 int num_completed_connections;   // Good
  • 类型和变量名一般为名词:如FileOpenernum_errors
    函数名通常是指令性的,如OpenFile()set_num_errors()
    取值函数是个特例,函数名和它要取值的变量同名。
  • 除非该缩写在其它地方都非常普遍,否则不要使用。永远不要用省略字母的缩写。
 int error_count;  // Good
 int error_cnt;    // Bad
  • 文件名要全部小写,可以包含下划线(_)或连字符 (-)。可接受的文件命名:my_useful_class.ccmy-useful-class.ccyusefulclass.cc,通常应尽量让文件名更加明确。
  • 类型名称的每个单词首字母均大写,不包含下划线,大驼峰法。
    所有类型命名:类、结构体、类型定义(typedef)、枚举->均使用相同约定。
  • 变量名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾。
    结构体的数据成员可以和普通变量一样,不用像类那样接下划线。
  • 常量名称前加k,如:const int kDaysInAWeek = 7;
  • 常规函数的函数名的每个单词首字母大写,没有下划线。
  • 取值和设值函数要与存取的变量名匹配。
  • int num_entries_;
  • int num_entries() const { return num_entries_; }
  • 枚举内数据的命名应当和常量或宏一致:kEnumName或是ENUM_NAME
  • 宏命名:全大小,下划线。如#define PI_ROUNDED 3.0

注释

  • 注释要言简意赅, 不要拖沓冗余, 复杂的东西简单化和简单的东西复杂化都是要被鄙视的。
  • 注释固然很重要, 但最好的代码本身应该是自文档化。有意义的类型名和变量名,要远胜过要用注释解释的含糊不清的名字。
  • 在每一个文件开头加入版权公告,然后是文件内容描述。
    每个文件都应该包含以下项, 依次是:
  • 版权声明 (比如,Copyright 2008 Google Inc.)
  • 许可证. 为项目选择合适的许可证版本 (比如,Apache 2.0, BSD, LGPL, GPL)
  • 作者: 标识文件的原始作者.
  • 通常,.h文件要对所声明的类的功能和用法作简单说明,.cc 文件通常包含了更多的实现细节或算法技巧讨论。如果你感觉这些实现细节或算法技巧讨论对于理解 .h 文件有帮助,可以该注释挪到 .h,并在.cc中指出文档在.h
    不要简单的在.h.cc间复制注释,这种偏离了注释的实际意义。
  • 每个类的定义都要附带一份注释,描述类的功能和用法。
  • 对于代码中巧妙的,晦涩的,有趣的,重要的地方加以注释。
  • 比较隐晦的地方要在行尾空两格进行注释。
  • 向函数传入NULL, 布尔值或整数时, 要注释说明含义, 或使用常量让代码望文知意:
 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);
  • 连续多行输入需要对齐:
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.
}
  • 注意永远不要用自然语言翻译代码作为注释。
  • TODO注释
// TODO(kl@gmail.com): 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.”)。

格式

  • 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码。
  • 返回类型和函数名在同一行, 参数也尽量放在同一行。
    函数看上去像这样:
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 {
  ...
}
  • 如果有些参数没有用到, 在函数定义处将参数名注释起来。
// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}
  • 函数调用遵循如下形式:
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);
  }
  • 条件语句,如if和左圆括号间都有个空格。右圆括号和左大括号之间也要有个空格。
  • switch语句:
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.
  • 指针和引用:句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格。
  • 逻辑与 (&&) 操作符总位于行尾:
if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another & last_one) {
  ...
}
  • return 表达式中不要用圆括号包围。
  • 预处理指令不要缩进,从行首开始。
    即使预处理指令位于缩进代码块中, 指令也应从行首开始。
// Good - directives at beginning of line
  if (lopsided_score) {
#if DISASTER_PENDING      // Correct -- Starts at beginning of line
    DropEverything();
#endif
    BackToNormal();
  }
  • 访问控制块的声明依次序是 public:, protected:, private:, 每次缩进 1 个空格。
  • 除第一个关键词(一般是public)外, 其他关键词前要空一行。这些关键词后不要保留空行。
  • 名字空间内容不缩进。
  • 垂直留白越少越好。这不仅仅是规则而是原则问题了: 不在万不得已, 不要使用空行,尤其是:
  • 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行。
  • 基本原则是: 同一屏可以显示的代码越多,越容易理解程序的控制流。
  • 当然,过于密集的代码块和过于疏松的代码块同样难看, 取决于你的判断. 但通常是垂直留白越少越好。

结束语

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

参考阅读

Google C++ 风格指南 - 中文版
Google 开源项目风格指南

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容