Google C++ 编码风格-笔记
头文件
1.#define保证
为了防止头文件重复包含。通常用#defined保证,符号是唯一的,格式通常为 <PROJECT>_<PATH>_<FILE>_H_。
#ifndef FOO_SRC_BAR_BAZ_H
#define FOO_SRC_BAR_BAZ_H
//todo...
#endif
2.前向声明
不要用类的前向声明,而是用#include"*.h"的方式。
3.内联函数
1.inline函数不要超过10行;
2.inline函数不要含有循环、switch语句。
4.#include的路径及顺序
1.包含文件的名称中不要使用.和..,而是用比较完整的项目路径,看上去清晰;
2.源文件#include的文件顺序(以空行分割)依次为:
(1)对应头文件;(2)c系统文件;(3)c++系统文件;(4)其他库.h文件
(5)本项目.h文件
这样可以保证项目的编译错误,最先发现。
作用域
1.使用方法
// In the .h file
namespace mynamespace {
// All declarations are within the namespace scope.
// Notice the lack of indentation.
class MyClass {
public:
...
void Foo()
};
} // namespace mynamespace //注释表示结束
// In the .cc file
namespace mynamespace {
// Definition of functions is within scope of the namespace.
void MyClass::Foo() {
...
}
} // namespace mynamespace
2.命名
1.可使用项目名称或相对路径;
//对于头文件 myproject/foo_bar.h , 应当使用
namespace myproject {
namespace foo_bar {
void Function1();
void Function2();
} // namespace foo_bar
} // namespace myproject
2.禁止使用using指示。滥用using指示会重新导致命名冲突;
using namespace myspaces; //禁止使用该方式
3.禁止使用inline namespace。
3.using声明与using指示
using std::cin;//using声明
using namespace std;//using 指示
using声明一次只能引入一个特定命名空间的成员。而且它的作用域是从声明点开始,直到包含该using声明作用域的末尾,外部作用域的同名实体被屏蔽(但是如果在同一作用域有同名实体,则会出现错误)。实际上一个using声明就如命名空间成员的局部别名一样。
#include"test.h"
using namespace std;
namespace MySpace{
int i=1;
}
int i=10;
int main()
{
//using MySpace::i;//正确,覆盖全局变量i=10,使i=1
using namespace MySpace;
i++; //错误,二义性
return 0;
}
4.匿名namespace和static作用与区别
匿名namespace和static都能使定义的全局变量只在当前文件中生效。当时两者实现的方式是有区别的。
namespace{ //匿名namespace
...
}
区别:
1.static修改的全局变量,它的链接方式变为内部链接,因此不能用来实例化模板;
2.匿名namespace为了能够实例化模板,并没有改变链接方式,还是外部链接。它是采用与C++重载的类似的方法,即名字改编来标识唯一的。
5.非成员函数、静态成员函数和全局函数
使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
6.局部变量
尽量置于最小作用域内,并在声明时初始化。
7.静态和全局变量
1.禁止定义静态生存周期的对象非POD 类型(含STL容器).
静态生存周期的对象,即包括了全局变量,静态变量,静态类成员变量和函数静态变量,都必须是原生数据类型 (POD : Plain Old Data): 即 int, char 和 float, 以及 POD 类型的指针、数组和结构体。
2.因为静态变量的构造函数、析构函数、初始化在C++中大部分顺序是不明确的。如果您确实需要一个 class 类型的静态或全局变量,可以考虑在 main() 函数或 pthread_once() 内初始化一个指针且永不回收。注意只能用 raw 指针
类
1.构造函数
不能在构造函数中调用虚函数。因为这种调用,不会重定位到派生类中的虚函数实现,从而导致当前没有子类化实现。
2.隐含类型转换
不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字
3.可拷贝类型和可移动类型
1.如果你的类型需要(用户可一眼看出是否可拷贝), 就让它们支持拷贝 / 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.
禁用方法如下:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
2.由于存在对象切割的风险,不要对有派生类的基类定义拷贝构造函数和赋值函数。如果有需求,可以提供一个public virtual clone()和一个protect的拷贝构造函数供派生类实现。
4.结构体 VS. 类
仅当只有数据成员时使用 struct, 其它一概使用 class
5.存取控制
将所有数据成员声明为 private, 除非是 static const 类型成员
函数
1.先输入参数后输出参数.
2.编写简短函数,不要超过40.
3.所有按引用传递的参数必须加const
void Foo(const string &in, string *out);
4.虚函数不可以有缺省参数,缺省参数的值始终保持一致,尽可能使用函数重载.
其他C++特性
1.用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.
2.用 const_cast 去掉 const 限定符.
3.流用来替代 printf() 和 scanf().
4.对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.
5.强烈建议你在任何可能的情况下都要使用 const
6.使用断言来指出变量为非负数, 而不是使用无符号型
7.使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.
8.整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 ‘\0’.
9.尽可能用 sizeof(varname) 代替 sizeof(type).
Struct data;
memset(&data, 0, sizeof(data));
auto
用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。
vector<string> v;
auto s1 = v[0]; // 创建一份 v[0] 的拷贝。
const auto& s2 = v[0]; // s2 是 v[0] 的一个引用。
sparse_hash_map<string, int>::iterator iter = m.find(val);
//修改为
auto iter = m.find(val);//好多了
命名规则
最重要的一致性规则是命名管理
通用命名规则
函数命名, 变量命名, 文件命名要有描述性; 少用缩写.
文件命名
1.文件名要全部小写, 可以包含下划线 () 或连字符 (-), 依照项目的约定. 如果没有约定, 那么 “” 更好.
//可接受的文件命名示例:
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
myusefulclass_test.cc
2.通常应尽量让文件名更加明确. http_server_logs.h 就比 logs.h 要好. 定义类时文件名一般成对出现, 如 foo_bar.h 和 foo_bar.cc, 对应于类 FooBar.
3.内联函数必须放在 .h 文件中
类型命名
1.类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.
2.所有类型命名 —— 类, 结构体, 类型定义 (typedef), 枚举, 类型模板参数 —— 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线.
// 类和结构体
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// 类型定义
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// using 别名
using PropertiesMap = hash_map<UrlTableProperties *, string>;
// 枚举
enum UrlTableErrors { ...
变量命名
变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
常量命名(包括静态变量/全局变量)
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:
const int kDaysInAWeek = 7;
函数命名(适用于类作用域与命名空间作用域的常量)
1.常规函数使用大小写混合: MyExcitingFunction(), MyExcitingMethod()
2.取值和设值函数的命名与变量一致. 一般来说它们的名称与实际的成员变量对应, 但并不强制要求. 例如 int count() 与 void set_count(int count).
命名空间命名
1.命名空间以小写字母命名.
2.建议使用更独特的项目标识符 (websearch::index, websearch::index_util) 而非常见的极易发生冲突的名称 (比如 websearch::util).
3.使用文件名以使得内部名称独一无二 (例如对于 frobber.h, 使用 websearch::index::frobber_internal).
枚举命名
枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
//常量(首选)
enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
//宏方式
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
注释
1.注释风格
使用 // 或 /* */, 统一就好.(自注:对于C++,除了函数定义和文档说明用/**/,其他的用//方式)
2.文件注释
3.类注释
3.函数注释
1.函数声明处注释的内容:
函数的输入输出.
对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
函数是否分配了必须由调用者释放的空间.
参数是否可以为空指针.
是否存在函数使用上的性能隐患.
如果函数是可重入的, 其同步前提是什么?
2.函数的定义处
注释重点要放在如何实现上.
4.变量注释
1.特别地, 如果变量可以接受 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_;
```
>2.全局变量
>和数据成员一样, 所有全局变量也要注释说明含义及用途, 以及作为全局变量的原因
###5.实现注释
>对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.
###6.TODO注释
>对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释.
TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份标识和与这一 TODO 相关的 issue. 主要目的是让添加注释的人 (也是可以请求提供更多细节的人) 可根据规范的 TODO 格式进行查找. 添加 TODO 注释并不意味着你要自己来修正, 因此当你加上带有姓名的 TODO 时, 一般都是写上自己的名字.
```
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
如果加 TODO 是为了在 “将来某一天做某事”, 可以附上一个非常明确的时间 “Fix by November 2005”), 或者一个明确的事项 (“Remove this code when all clients can handle XML responses.”).
7.弃用注释
通过弃用注释(DEPRECATED comments)以标记某接口点已弃用.
在 DEPRECATED 一词后, 在括号中留下您的名字, 邮箱地址以及其他身份标识.弃用注释应当包涵简短而清晰的指引, 以帮助其他人修复其调用点. 在 C++ 中, 你可以将一个弃用函数改造成一个内联函数, 这一函数将调用新的接口.
仅仅标记接口为 DEPRECATED 并不会让大家不约而同地弃用, 还得亲自主动修正调用点
格式化
1.空格还是制表位
只使用空格, 每次缩进 2 个空格.我们使用空格缩进. 不要在代码中使用制表符. 你应该设置编辑器将制表符转为空格.
2.函数声明与定义
返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行, 分行方式与 函数调用 一致.
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 个空格的缩进.
未被使用的参数如果其用途不明显的话, 在函数定义处将参数名注释起来:
void Circle::Rotate(double /*radians*/) {}
3.if-else
1.if 和左圆括号间都有个空格
2.简短的条件语句允许写在同一行. 只有当语句简单并且没有使用 else 子句时使用.
3.如果语句中某个 if-else 分支使用了大括号的话, 其它分支也必须使用
4.循环和开关选择语句
switch (var)
{
case 0: // 2 空格缩进
{
... // 4 空格缩进
break;
}
case 1:
{
...
break;
}
default:
{
assert(false);
}
}
//空循环体应使用 {} 或 continue, 而不是一个简单的分号.
while (condition)
{
// 反复循环直到条件失效.
}
for (int i = 0; i < kSomeNumber; ++i) {} // 可 - 空循环体.
while (condition) continue; // 可 - contunue 表明没有逻辑.
5.布尔表达式
//逻辑与 (&&) 操作符总位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one)
{
...
}
6.类格式
- 所有基类名应在 80 列限制下尽量与子类名放在同一行.
- 关键词 public:, protected:, private: 要缩进 1 个空格.
- 除第一个关键词 (一般是 public) 外, 其他关键词前要空一行. 如果类比较小的话也可以不空.
- 这些关键词后不要保留空行.
- public 放在最前面, 然后是 protected, 最后是 private.
7.构造函数初始值列表
构造函数初始化列表放在同一行或按四格缩进并排多行.
// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// 如果不能放在同一行,
// 必须置于冒号后, 并缩进 4 个空格
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// 如果初始化列表需要置于多行, 将每一个成员放在单独的一行
// 并逐行对齐
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
}
// 右大括号 } 可以和左大括号 { 放在同一行
// 如果这样做合适的话
MyClass::MyClass(int var)
: some_var_(var) {}
命令空间格式化
命名空间内容不缩进.
namespace {
void foo() { // 正确. 命名空间内没有额外的缩进.
...
}
} // namespace
//声明嵌套命名空间时, 每个命名空间都独立成行.
namespace foo {
namespace bar {
例外情况
- 不要使用匈牙利命名法 (比如把整型变量命名成 iNum). 使用 Google 命名约定, 包括对源文件使用 .cc 扩展名.
- Windows 定义了很多原生类型的同义词 (YuleFox 注: 这一点, 我也很反感), 如 DWORD, HANDLE 等等. 在调用 Windows API 时这是完全可以接受甚至鼓励的. 即使如此, 还是尽量使用原有的 C++ 类型, 例如使用 const TCHAR * 而不是 LPCTSTR.
- 使用 Microsoft Visual C++ 进行编译时, 将警告级别设置为 3 或更高, 并将所有警告(warnings)当作错误(errors)处理.
- 不要使用 #pragma once; 而应该使用 Google 的头文件保护规则. 头文件保护的路径应该相对于项目根目录 (Yang.Y 注: 如 #ifndef SRC_DIR_BAR_H_, 参考 #define 保护 一节).
- 除非万不得已, 不要使用任何非标准的扩展, 如 #pragma 和 __declspec. 使用 __declspec(dllimport) 和 __declspec(dllexport) 是允许的, 但必须通过宏来使用, 比如 DLLIMPORT 和 DLLEXPORT, 这样其他人在分享使用这些代码时可以很容易地禁用这些扩展.
源代码风格检测
下载
下载cpplint源码
下载python2.7(必须使用该版本,3.6版本无效)