GOOGLE C++ 规范读书笔记
1、头文件
1、使用#define避免头文件被重复包含,定义的宏为全路径+文件名;
2、能使用前置声明的地方尽量不使用#include。即如果不需要访问类定义的情况下,使用类的前置声明即可;需要类定义时,则必须得包含头文件。
3、只有当函数只有10行甚至更少时才将其定义为内联函数;内联包含循环或switch语句的函数常常是得不尝失(除非在大多数情况下,这些循环或switch语句从不被执行);虚函数和递归函数就不会被正常内联,这是因为内联函数需要编译时就确定函数体,而递归函数在编译时递归层数未知,虚函数不可以内联是为什么?
4、内联函数必须放在头文件中,编译器才能在调用内联函数展开定义。如果内联函数的定义比较复杂,则可以放在后缀名为-inl.h的头文件中;
5、定义函数时,参数顺序依次为:输入参数,然后是输出参数。
6、使用标准的头文件包含顺序可增强可读性,避免隐藏依赖:C库,C++库,其他库的.h,本项目的.h;例如,又如, dir/foo.cc 的主要作用是实现或测试 dir2/foo2.h 的功能, foo.cc 中包含头文件的次序如下:
①dir2/foo2.h (优先位置, 详情如下)
②C 系统文件
③C++ 系统文件
④其他库的 .h 文件
⑤本项目内 .h 文件
这种排序方式可有效减少隐藏依赖. 我们希望每一个头文件都是可被独立编译的 (yospaly 译注: 即该头文件本身已包含所有必要的显式依赖), 最简单的方法是将其作为第一个 .h 文件 #included 进对应的 .cc.
2、作用域
2.1 鼓励在cpp文件内使用匿名名字空间,使用具名的名字空间时,其名称可给予项目名或相对路径,不要使用using关键字。
2.2 C++ 中存在唯一定义原则,注意写注释标示定义的结束
2.3 为什么引用命名空间之后,会导致命名空间中其他函数的都可用,而导致命名空间污染。
2.4 嵌套类定义和使用,除非嵌套类是接口的一部分,否则嵌套类不应该声明为公用的。
2.5 对于非成员函数,静态函数有时不用类封装时,可以使用命名空间来封装。
2.6 局部变量尽可能地在使用的时候声明,在定义的时候进行初始化,同时还要兼顾效率方面的考虑;
2.7 只允许使用plain old data类的静态数据,不允许使用函数返回值初始化静态数据类型
2.8 多线程的全局变量,不要使用class类型(含STL容器),避免不明确行为导致的BUG。
2.9 作用域,除了考虑名字污染,可读性外,主要是为了降低耦合,提高编译/执行效率。
3、类
3.1 如果在构造函数中进行的初始化过于复杂时,建议创建Init()来显式地进行初始化,构造函数禁止调用虚构函数防止不能进行正常的多态;不在构造函数中做太多逻辑相关的初始化;
3.2 如果类中有成员变量,那么最好设置一个默认的构造函数,否则编译器将会为这个类自动生成一个,那将会非常危险;如果继承父类的子类,没有增加数据成员的话,则不需要增加默认构造函数。
3.3 可以使用explicit关键字防止隐式转换,单形参的构造函数都应该声明为explicit;
3.4 指针和引用可以替代copy构造函数和赋值操作符的功能,有时在容器中使用指针或者应用会是一个更好的选择;如果不需要copy构造函数和赋值操作符,那么需要显示地禁止他们 ,即将他们声明为私有的,而不定义;
3.5 当且仅当有数据时,使用struct,其他一律使用classs;
3.6 组合通常比继承更加合适,当必须得是继承的话,则是共有继承;C++中继承分类两类:实现继承和借口继承;如果类中有虚函数,那么类的析构函数最好是虚函数;
3.7 接口是指满足特定条件的类,这些类以Interface为后缀;只有纯虚函数,或者静态函数,不存在非静态函数;不可以直接被实例化;
3.8 尽量少使用重载,模板;
3.9 在头文件中,将存取函数设置为内联函数;
3.10 尽量使函数体控制在一个合理的水平,使程序简短易修改,函数体尽量短小,紧凑,功能单一。
4 GOOGLE 技巧
4.1 尽量避免使用智能指针,使用时也尽量局部化,安全第一;安全指针的类型;区别scoped_ptr,shared_ptr,auto_ptr
4.2 cpplint.py 是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具.
5 C++ 其他特性
5.1 所有引用传递的参数都应该采用const传递;
5.2 输入参数是值类型或者const ref引用,输出参数是指针类型;
5.3 仅输入参数类型不同,功能相同时使用重载函数。
5.4 不允许使用缺省函数参数;
5.5 尽量不使用变长数组和allocate;
5.6 允许合理使用友元类和友元函数;
5.7 不允许使用C++异常处理;
5.8 Google禁用出于自己项目管理的考虑;
5.9 如果实现根据子类类型来确定执行不同逻辑代码,虚函数无疑更加合适,对象内部就可处理类型识别问题;
5.10 使用C++的类型转化,如static_cast<>().不要使用 int y = (int)x;等转换方式;
5.11 仅在记录日志时,使用流;大多数人选择printf+ read/write;
5.12 对于迭代器和模板类型使用前置自增或者自减;(copy代价过大)
5.13 尽量使用const,mutable可以使用,但是其在多线程中是不安全的;
5.14 使用断言指出变量为非负数,而不是使用无符号型;
5.15 使用宏时要非常谨慎,尽量以内联函数,枚举和常量代替之;
5.16 不要再头文件中定义宏,在马上使用时才进行#define,使用完之后,进行#undef;
5.17 尽可能使用sizeof(varname)代替sizeof(type);
5.18 只使用Boost中被认可的库。
6 命名
6.1 类型和变量最好是名词,函数名最好是动词;除非缩写在其他地方都非常普遍,否则不要缩写;永远不要用省略字母的缩写;
6.2 文件名可以使用小写字母及下划线,或者虚线。源文件采用.cc,头文件采用.h。让文件名非常具体。内联函数最好都在头文件中;
6.3 类型名:首字母大写,不用下划线;
6.4 变量名:小写字母,下划线连接单词;全局变量的最好采用g_来标识;
6.5 采用k开头,作为常量的开头,例如const int kDaysInWeek = 7;
6.6 常规函数使用大小写混合,取值和设值函数则要求与变量名匹配:MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().
6.7 名字空间命名:采用小写字母,由项目名称+目录组成;
6.8 枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
6.9 采用大写字母定义宏。
7 注释
7.1 好的程序是自我文档;
7.2 注意注释的一致性,也就是保持一致的注释风格;
7.3 每一个文件都应该包含下列信息:① 版权信息;② 授权信息;③ 原始文件的作者;在这三个信息之下,应该有一段用于描述文件用途的话;不能使头文件和实现文件中注释不能一样,容易造成误解;
7.4 每一个类定义的地方,必须得有该类是如何使用的说明;
7.5 函数声明注释函数功能,以及函数实现;
7.6 通常变量名本身就能很好地说明变量的用途 ,但是有时需要注释一下;类数据成员和全局变量得注释一下说明其用途;
7.7 对代码中巧妙的,重要的,有趣的,不容易懂的地方注释;
7.8 永远不要翻译代码作为翻译,语法和标点都正确的句子对于理解代码大有裨益;
7.9 对于临时,短期的方案,但是还是不够好的,我们采用TODO;
7.10 TODO标示将要进行的工作。
8 格式
8.1 每一行代码字符数不超过80个字符;
8.2 尽量不使用非ASCII字符,使用时 必须使用UTF-8编码;
8.3 只使用空格,每次缩进两个空格;
8.4 返回类型和函数名在同一行,参数也尽量在同一行;
返回值总是和函数名在同一行;左圆括号总是和函数名在同一行;函数名和左圆括号间没有空格;圆括号与参数间没有空格;左大括号总在最后一个参数同一行的末尾处;右大括号总是单独位于函数最后一行;右圆括号和左大括号间总是有一个空格;函数声明和实现处的所有形参名称必须保持一致;所有形参应尽可能对齐;缺省缩进为 2 个空格;换行后的参数保持 4 个空格的缩进;
8.5 尽量将函数调用放在同一行,否则将实参放在圆括号中;
8.6 句点或箭头前后不要有空格键,指针或地址操作符之后不能有空格键;
8.7 函数返回值不要使用圆括号;
8.8 预处理指令不要缩进,从行首开始;
8.9 访问控制块的声明次序依次是:public,protected,private,每次缩进一个空格;
8.10 构造函数初始化列表放在同一行,或者按四格缩进并排几行;
8.11 名字空间内容不要缩进,其内的函数也不要缩进。
9 规则例外
9.1 对于现有不符合既定编程风格的代码可以网开一面;
9.2 不要使用任何非标准的扩展,不到万不得以;
9.3 运用常识和判断力并保持一致。