代码风格规范-谷歌

  1. Google C++ 风格指南
    1. 头文件
      • self-contained头文件:头文件本身依赖的其它头文件,需要全部包含。
      • #define保护:<PROJECT>_<PATH>_<FILE>_H_ 
        1. #ifndef FOO_BAR_BAZ_H_
        2. #define FOO_BAR_BAZ_H_
        3. ...
        4. #endif // FOO_BAR_BAZ_H_
      • 前置声明:尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可
      • 内联函数:只有当函数只有 10 行甚至更少时才将其定义为内联函数.
      • #include 的路径及顺序:相关头文件, C 库, C++ 库, 其他库的 .h, 本项目内的 .h.
    2. 作用域
      • 命名空间:鼓励在 .cc 文件内使用匿名命名空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace)。
      • 匿名命名空间和静态变量:在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 static 。但是不要在 .h 文件中这么做。
        1. 匿名命名空间说白了就是文件作用域,就像 C static 声明的作用域一样,后者已经被 C++ 标准提倡弃用。
      • 非成员函数、静态成员函数和全局函数:使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
      • 局部变量:将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化.
      • 静态和全局变量:禁止定义静态储存周期非POD变量,禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植。
        1. 静态生存周期的对象,即包括了全局变量,静态变量,静态类成员变量和函数静态变量,都必须是原生数据类型 (POD : Plain Old Data): 即 int, char 和 float, 以及 POD 类型的指针、数组和结构体
      • 构造函数的职责:不要在构造函数中调用虚函数(不会重定向到子类的虚函数), 也不要在无法报出错误时进行可能失败的初始化.
      • 隐式类型转换:不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字
      • 可拷贝类型和可移动类型:如果你的类型需要, 就让它们支持拷贝 / 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.
      • 结构体 VS. 类:仅当只有数据成员时使用 struct, 其它一概使用 class.
      • 继承:使用组合常常比使用继承更合理. 如果使用继承的话, 定义为 public 继承.
      • 多重继承:真正需要用到多重实现继承的情况少之又少. 只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类; 其它基类都是以 Interface 为后缀的 纯接口类.
      • 接口:接口是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制).
      • 运算符重载:除少数特定环境外, 不要重载运算符. 也不要创建用户定义字面量(不要重载 operator"").
      • 存取控制:将 所有 数据成员声明为 private, 除非是 static const 类型成员 (遵循 常量命名规则). 处于技术上的原因, 在使用 Google Test 时我们允许测试固件类中的数据成员为 protected.
      • 声明顺序:将相似的声明放在一起, 将 public 部分放在最前,其次是protected.
    3. 函数
      • 参数顺序:函数的参数顺序为: 输入参数在先, 后跟输出参数.
      • 编写简短函数:我们倾向于编写简短, 凝练的函数.
      • 引用参数:所有按引用传递的参数必须加上 const.
      • 函数重载:若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.
      • 缺省参数:只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与 函数重载 遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下.
      • 函数返回类型后置语法:只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法. auto foo(int x) -> int;
    4. 来自 Google 的奇技
      • 所有权与智能指针:动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.
        1. 唯一指针+move()
        2. 共享指针+弱指针
      • Cpplint:使用 cpplint.py 检查风格错误.在行尾加 // NOLINT, 或在上一行加 // NOLINTNEXTLINE, 可以忽略报错.
    5. 其他 C++ 特性
      • 右值引用:只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.
      • 变长数组和 alloca():我们不允许使用变长数组和 alloca().
      • 友元:我们允许合理的使用友元类及友元函数.
      • 异常:我们不使用 C++ 异常.
      • 运行时类型识别:我们禁止使用 RTTI.RTTI 允许程序员在运行时识别 C++ 类对象的类型. 它通过使用 typeid 或者 dynamic_cast 完成.
      • 类型转换:使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式;
      • 流:不要在代码中使用流,只在记录日志时使用流.尽量使用sanf/printf + read/write
      • 前置自增和自减:对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.
      • const 用法:我们强烈建议你在任何可能的情况下都要使用 const. 此外有时改用 C++11 推出的 constexpr 更好。
      • constexpr 用法:在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。
      • 整型:C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t. 此外要留意,哪怕您的值并不会超出 int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。
      • 64 位下的可移植性:代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记。
      • 预处理宏:使用宏时要非常谨慎, 尽量以内联函数inline, 枚举enums和const代替之.
      • 0, nullptr 和 NULL:整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 '\0'.
      • 尽可能用 sizeof(varname) 代替 sizeof(type).
        1. Struct data; sizeof(data) sizeof(Struct)
      • auto:用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方
        1. auto s1 = v[0];  // 创建一份 v[0] 的拷贝。
        2. const auto& s2 = v[0];  // s2 是 v[0] 的一个引用。
      • 列表初始化:你可以用列表初始化。千万别直接列表初始化 auto 变量 auto d = double{1.23}
      • Lambda 表达式:适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来
      • 模板编程:不要使用复杂的模板编程
      • Boost 第三方库:只使用 Boost 中被认可的库.https://www.boost.org/
    6. 命名约定
      • 通用命名规则:尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词.
      • 文件命名:文件名要全部小写, 可以包含下划线 (_) 或连字符 (-), 依照项目的约定. 如果没有约定, 那么 “_” 更好.
      • 类型命名(结构体/类/typedef/enums/using A=B/类型模板参数):每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.
      • 变量命名:变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
        1. 普通变量命名/结构体变量:string table_name;  // 好-用下划线. string tablename; // 好 - 全小写.
        2. 类数据成员:
        3. 常量命名:声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合。 const int kDaysInAWeek = 7;
      • 函数命名/枚举命名:一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc() 而非 StartRPC()).
      • 命名空间命名:命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称. 要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突.顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字. 命名空间中的代码, 应当存放于和命名空间的名字匹配的文件夹或其子文件夹中.注意 不使用缩写作为名称 的规则同样适用于命名空间. 命名空间中的代码极少需要涉及命名空间的名称, 因此没有必要在命名空间中使用缩写.
      • 宏命名:MY_MACRO_THAT_SCARES_SMALL_CHILDREN
    7. 注释:使用 // 或 /* */, 统一就好.
      • 文件注释:在每一个文件开头加入版权公告,法律公告和作者信息
      • 类注释:
      • 函数注释
        1. 函数的输入输出.
        2. 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
        3. 函数是否分配了必须由调用者释放的空间.
        4. 参数是否可以为空指针.
        5. 是否存在函数使用上的性能隐患.
        6. 如果函数是可重入的, 其同步前提是什么?
      • 变量注释
      • 实现注释:对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.
        1. 代码前注释
        2. 行注释
        3. 函数参数注释
        4. 不允许的行为
      • 标点, 拼写和语法:注意标点, 拼写和语法; 写的好的注释比差的要易读的多.
      • TODO 注释
      • 弃用注释:DEPRECATED  
    8. 格式
      • 行长度:每一行代码字符数不超过 80.
      • 非 ASCII 字符:尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.
      • 空格还是制表位:只使用空格, 每次缩进 2 个空格.你应该设置编辑器将制表符转为空格.
      • 函数声明与定义:返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行, 分行方式与函数调用一致.
      • 函数调用/列表初始化格式
        1. 如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:
      • 条件语句
        1. 注意所有情况下 if 和左圆括号间都有个空格. 右圆括号和左大括号之间也要有个空格 
        2. 简短的条件语句允许写在同一行. 只有当语句简单并且没有使用 else 子句时使用
        3. 但如果语句中某个 if-else 分支使用了大括号的话, 其它分支也必须使用
      • 循环和开关选择语句
        1. switch 语句可以使用大括号分段, 以表明 cases 之间不是连在一起的
        2. 在单语句循环里, 括号可用可不用.
        3. 空循环体应使用 {} 或 continue,而不是一个简单的分号 
      • 指针和引用表达式
        1. 句点或箭头前后不要有空格
        2. 指针/地址操作符 (*, &) 之后不能有空格 
      • 布尔表达式
        1. 如果一个布尔表达式超过 标准行宽, 断行方式要统一一下:逻辑与 (&&) 操作符总位于行尾 
      • 函数返回值:不要在 return 表达式里加上非必须的圆括号.
      • 变量及数组初始化
        1. 用 =, () 和 {} 均可.
        2. 请务必小心列表初始化,请改用括号:
      • 预处理指令:即使预处理指令位于缩进代码块中, 指令也应从行首开始 
      • 类格式:
        1. 访问控制块的声明依次序是 public:, protected:, private:, 每个都缩进 1 个空格.
      • 构造函数初始值列表
      • 命名空间格式化:命名空间内容不缩进.
      • 水平留白:水平留白的使用根据在代码中的位置决定. 永远不要在行尾添加没意义的留白.
      • 垂直留白:垂直留白越少越好.
        1. 函数体内开头或结尾的空行可读性微乎其微.
        2. 在多重 if-else 块里加空行或许有点可读性.

 

/Google C++ 风格指南-简短笔记/

(1)避免多重包含是学编程时最基本的要求;
(2)前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;
(3)内联函数的合理使用可提高代码执行效率;
(4)-inl.h 可提高代码可读性 (一般用不到吧:D);
(5)标准化函数参数顺序可以提高可读性和易维护性 (对函数参数的堆栈空间有轻微影响, 我以前大多是相同类型放在一起);
(6)包含文件的名称使用 . 和 .. 虽然方便却易混乱, 使用比较完整的项目路径看上去很清晰, 很条理, 包含文件的次序除了美观之外, 最重要的是可以减少隐藏依赖, 使每个头文件在 “最需要编译” (对应源文件处 :D) 的地方编译, 有人提出库文件放在最后, 这样出错先是项目内的文件, 头文件都放在对应源文件的最前面, 这一点足以保证内部错误的及时发现了.
(7)原来还真有项目用 #includes 来插入文本,且其文件扩展名 .inc 看上去也很科学。
(8)Google 已经不再提倡 -inl.h 用法。
(9)注意,前置声明的类是不完全类型(incomplete type),我们只能定义指向该类型的指针或引用,或者声明(但不能定义)以不完全类型作为参数或者返回类型的函数。毕竟编译器不知道不完全类型的定义,我们不能创建其类的任何对象,也不能声明成类内部的数据成员。
(10)类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的 .cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。
(11)在 #include 中插入空行以分割相关头文件, C 库, C++ 库, 其他库的 .h 和本项目内的 .h 是个好习惯。
(12)cc 中的匿名命名空间可避免命名冲突, 限定作用域, 避免直接使用 using 关键字污染命名空间;
(13)嵌套类符合局部使用原则, 只是不能在其他头文件中前置声明, 尽量不要 public;
(14)尽量不用全局函数和全局变量, 考虑作用域和命名空间限制, 尽量单独形成编译单元;
(15)多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug.
(16)作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率.
(17)注意「using 指示(using-directive)」和「using 声明(using-declaration)」的区别。
(18)匿名命名空间说白了就是文件作用域,就像 C static 声明的作用域一样,后者已经被 C++ 标准提倡弃用。
(19)局部变量在声明的同时进行显式值初始化,比起隐式初始化再赋值的两步过程要高效,同时也贯彻了计算机体系结构重要的概念「局部性(locality)」。
(20)注意别在循环犯大量构造和析构的低级错误。
(21)不在构造函数中做太多逻辑相关的初始化;
(22)编译器提供的默认构造函数不会对变量进行初始化, 如果定义了其他构造函数, 编译器不再提供, 需要编码者自行提供默认构造函数;
(23)为避免隐式转换, 需将单参数构造函数声明为 explicit;
(24)为避免拷贝构造函数, 赋值操作的滥用和编译器自动生成, 可将其声明为 private 且无需实现;
(25)仅在作为数据集合时使用 struct;
(26)组合 > 实现继承 > 接口继承 > 私有继承, 子类重载的虚函数也要声明 virtual 关键字, 虽然编译器允许不这样做;
(27)避免使用多重继承, 使用时, 除一个基类含有实现外, 其他基类均为纯接口;
(28)接口类类名以 Interface 为后缀, 除提供带实现的虚析构函数, 静态成员函数外, 其他均为纯虚函数, 不定义非静态数据成员, 不提供构造函数, 提供的话, 声明为 protected;
(29)为降低复杂性, 尽量不重载操作符, 模板, 标准类中使用时提供文档说明;
(30)存取函数一般内联在头文件中;
(31)声明次序: public -> protected -> private;
(32)函数体尽量短小, 紧凑, 功能单一;
(33)把智能指针当成对象来看待的话, 就很好领会它与所指对象之间的关系了.
(34)原来 Rust 的 Ownership 思想是受到了 C++ 智能指针的很大启发啊.
(35)scoped_ptr 和 auto_ptr 已过时. 现在是 shared_ptr 和 uniqued_ptr 的天下了.
(36)按本文来说, 似乎除了智能指针, 还有其它所有权机制, 值得留意.
(37)Arch Linux 用户注意了, AUR 有对 cpplint 打包.
(38)实际上,缺省参数会改变函数签名的前提是改变了它接收的参数数量,比如把 void a() 改成 void a(int b = 0), 开发者改变其代码的初衷也许是,在不改变「代码兼容性」的同时,又提供了可选 int 参数的余地,然而这终究会破坏函数指针上的兼容性,毕竟函数签名确实变了。
(39)此外把自带缺省参数的函数地址赋值给指针时,会丢失缺省参数信息。
(40)我还发现 滥用缺省参数会害得读者光只看调用代码的话,会误以为其函数接受的参数数量比实际上还要少。
(41)friend 实际上只对函数/类赋予了对其所在类的访问权限,并不是有效的声明语句。所以除了在头文件类内部写 friend 函数/类,还要在类作用域之外正式地声明一遍,最后在对应的 .cc 文件加以定义。
(42)本风格指南都强调了「友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类」。那么可以把其声明放在类声明所在的头文件,定义也放在类定义所在的文件。
(43)由于友元函数/类并不是类的一部分,自然也不会是类可调用的公有接口,于是我主张全集中放在类的尾部,即的数据成员之后,参考 声明顺序 。
(44)对使用 C++ 异常处理应具有怎样的态度? 非常值得一读。
(45)注意初始化 const 对象时,必须在初始化的同时值初始化。
(46)使用断言来指出变量为非负数, 而不是使用无符号型!
(47)auto 在涉及迭代器的循环语句里挺常用。
(48)Should the trailing return type syntax style become the default for new C++11 programs? 讨论了 auto 与尾置返回类型一起用的全新编码风格,值得一看。
(49)感觉 Google 的命名约定很高明, 比如写了简单的类 QueryResult, 接着又可以直接定义一个变量 query_result, 区分度很好; 再次, 类内变量以下划线结尾, 那么就可以直接传入同名的形参, 比如 TextQuery::TextQuery(std::string word) : word_(word) {} , 其中 word_ 自然是类内私有成员.
(50)关于注释风格, 很多 C++ 的 coders 更喜欢行注释, C coders 或许对块注释依然情有独钟, 或者在文件头大段大段的注释时使用块注释;
(51)文件注释可以炫耀你的成就, 也是为了捅了篓子别人可以找你;
(52)注释要言简意赅, 不要拖沓冗余, 复杂的东西简单化和简单的东西复杂化都是要被鄙视的;
(53)对于 Chinese coders 来说, 用英文注释还是用中文注释, it is a problem, 但不管怎样, 注释是为了让别人看懂, 难道是为了炫耀编程语言之外的你的母语或外语水平吗;
(54)注释不要太乱, 适当的缩进才会让人乐意看. 但也没有必要规定注释从第几列开始 (我自己写代码的时候总喜欢这样), UNIX/LINUX 下还可以约定是使用 tab 还是 space, 个人倾向于 space;
(55)TODO 很不错, 有时候, 注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索, 就知道还有哪些活要干, 日志都省了.
(56)对于代码格式, 因人, 系统而异各有优缺点, 但同一个项目中遵循同一标准还是有必要的;
(57)行宽原则上不超过 80 列, 把 22 寸的显示屏都占完, 怎么也说不过去;
(58)尽量不使用非 ASCII 字符, 如果使用的话, 参考 UTF-8 格式 (尤其是 UNIX/Linux 下, Windows 下可以考虑宽字符), 尽量不将字符串常量耦合到代码中, 比如独立出资源文件, 这不仅仅是风格问题了;
(59)UNIX/Linux 下无条件使用空格, MSVC 的话使用 Tab 也无可厚非;
(60)函数参数, 逻辑条件, 初始化列表: 要么所有参数和函数名放在同一行, 要么所有参数并排分行;
(61)除函数定义的左大括号可以置于行首外, 包括函数/类/结构体/枚举声明, 各种语句的左大括号置于行尾, 所有右大括号独立成行;
(62)./-> 操作符前后不留空格, */& 不要前后都留, 一个就可, 靠左靠右依各人喜好;
(63)预处理指令/命名空间不使用额外缩进, 类/结构体/枚举/函数/语句使用缩进;
(64)初始化用 = 还是 () 依个人喜好, 统一就好;
(65)return 不要加 ();
(66)水平/垂直留白不要滥用, 怎么易读怎么来.
(67)关于 UNIX/Linux 风格为什么要把左大括号置于行尾 (.cc 文件的函数实现处, 左大括号位于行首), 我的理解是代码看上去比较简约, 想想行首除了函数体被一对大括号封在一起之外, 只有右大括号的代码看上去确实也舒服; Windows 风格将左大括号置于行首的优点是匹配情况一目了然.
(68)80 行限制事实上有助于避免代码可读性失控, 比如超多重嵌套块, 超多重函数调用等等.
(69)Linux 上设置好了 Locale 就几乎一劳永逸设置好所有开发环境的编码, 不像奇葩的 Windows.
(70)Google 强调有一对 if-else 时, 不论有没有嵌套, 都要有大括号. Apple 正好 有栽过跟头 .
(71)其实我主张指针/地址操作符与变量名紧邻, int* a, b vs int *a, b, 新手会误以为前者的 b 是 int * 变量, 但后者就不一样了, 高下立判.
(72)在这风格指南里我才刚知道 C++ 原来还有所谓的 Alternative operator representations, 大概没人用吧.
(73)注意构造函数初始值列表(Constructer Initializer List)与列表初始化(Initializer List)是两码事, 我就差点混淆了它们的翻译.
(74)事实上, 如果您熟悉英语本身的书写规则, 就会发现该风格指南在格式上的规定与英语语法相当一脉相承. 比如普通标点符号和单词后面还有文本的话, 总会留一个空格; 特殊符号与单词之间就不用留了, 比如 if (true) 中的圆括号与 true.
(75)本风格指南没有明确规定 void 函数里要不要用 return 语句, 不过就 Google 开源项目 leveldb 并没有写; 此外从 Is a blank return statement at the end of a function whos return type is void necessary? 来看, return; 比 return ; 更约定俗成(事实上 cpplint 会对后者报错, 指出分号前有多余的空格), 且可用来提前跳出函数栈.

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值