Google编程规范总结
- 一、前言
- 二、头文件
- 三、类
- 构造函数
- 可拷贝类型和可移动类型
- struct和class
- 接口
- 存取控制和声明顺序
- 四、来自Google的奇技
- 所有权与智能指针
- cpplint
- 五、其他C++特性
- 六、命名约定(重点)
- 七、注释(重点)
- 八、代码格式(重点)
- 九、结束语
一、前言
昨天听了雷军的演讲,感受还是很多的,最大的感想就是我之前很喜欢的一句话:在奋力往前走的时候别忘了有时要抬头看看天。努力很重要,甚至是非常重要,但是方向和把握机会的能力也是至关重要的。
最近在看Google Style Guide,感觉得记录一下,来规范一下自己在写C++程序时候的习惯,最近也是放假在家闲出毛病来了,想着提升下自己。
距离6个月,我终于在今天(20230105)把这个坑填上了,希望大家喜欢。重点可以看看第六部分的命名规范,文件命名,类型命名,变量命名,函数命名。
可以的话希望大家点点赞或者关注哟!
二、头文件
- self-contain 头文件:就是头文件要能自给自足,也就是要包含它所需要的所有其他的头文件
- define保护:所有头文件都要有#define来保护头文件被多重包含,为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径,例如:项目 foo 中的头文件 foo/src/bar/
baz.h 可按如下方式保护:#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ … #endif // FOO_BAR_BAZ_H_
- 尽量避免使用前置声明:所谓「前置声明」(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义。
具体的定义和说明:forword declaration
类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小,成员等具体内容。在未提供完整的类之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。而头文件则一一告之。 - 内联函数:函数只有10行甚至更少的时候才将其定义为内联函数。当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用。对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联.内联那些包含循环或 switch 语句的函数常常是得不偿失
- include的路径和顺序:相关头文件, C 库, C++ 库, 其他库的 .h, 本项目内的 .h,他们之间一般是用插入空行来区分。
平台特定的条件编译一般是放在其他 include 之后。(有人提出把库文件放在最后,这样出错先是项目内的文件)。
三、类
构造函数
- 不要在构造函数中进行复杂的初始化,因为构造函数很难上报错误,不能使用异常,操作失败会造成对象初始化失败,进入不确定状态。
- 对单个参数的构造函数使用explicit关键字来避免隐式转换,这一规则也适用于除第一个参数以外的其他参数都具有默认参数的构造函数,例如
Foo::Foo(string name, int id = 42).
可拷贝类型和可移动类型
- 如果你的类型需要, 就让它们支持拷贝/ 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.
可拷贝:** 拷贝操作一般通过拷贝构造函数与拷贝赋值操作符定义**,
可移动:移动操作一般是通过移动构造函数和移动赋值操作符实现的. - 如果你的类不需要拷贝/ 移动操作, 请显式地通过 = delete 或其他手段禁用之
struct和class
仅当只有数据时使用struct,其余时间一概用class。
为了和 STL 保持一致, 对于仿函数和 trait 特性可以不用 class 而是使用 struct.
对于如何实现trait类型可以看这篇文章:trait类型
接口
- 接口需要满足以下条件:
只有纯虚函数 (“=0”) 和静态函数 (除了下文提到的析构函数)
没有非静态数据成员.
没有定义任何构造函数. 如果有, 也不能带有参数, 并且必须为 protected
如果它是一个子类, 也只能从满足上述条件的类继承
存取控制和声明顺序
说白了就是数据成员声明为private,然后为每个数据成员写set和get函数来进行控制。
在类中使用特定的声明顺序: public: 在 private: 之前, 成员函数在数据成员 (变量) 前;
四、来自Google的奇技
(书上是这么翻译的,感觉挺有意思的)
所有权与智能指针
动态分配出的对象最好有单一且固定的所有主,然后通过智能指针传递所有权。
可以把智能指针当成一个重载了 * 和 -> 的「对象」来看。。std::unique_ptr 是 C++11 新推出的一种智能指针类型,用来表示
动态分配出的对象的「独一无二」所有权;当 std::unique_ptr 离开作用域,对象就会被销毁。
不能复制 std::unique_ptr, 但可以把它移动(move)给新所有主。std::shared_ptr 同样表示动态分配对象的所有权,但可以被共享,也可以被复制;对象的所有权由所有复制者共同拥有,最后一个复制者被销毁时,对象也会随着被销毁。
如果对性能要求比较高且此对象是不可更改的(比如说 std::shared_ptr< const Foo >),这时可以用共享所有权来避免昂贵的拷贝操作
cpplint
一个用来检查风格错误的程序,我找个机会专门写一篇文章介绍使用
五、其他C++特性
(这本书暂时只到了C++11)
- 所有按引用传递的参数尽量加上const
- 只在定义移动构造函数与移动赋值操作时使用右值引用,右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能, 无论其参数是否是临时对象都能正常工作,右值引用能实现可移动但不可拷贝的类型, 这一特性对那些在拷贝方面没有实际需求, 但有时又需要将它们作为函数参数传递或塞入容器的类型很有用(完美转发)
- 缺省参数:尽可能使用函数重载来代替缺省参数,缺省参数会干扰函数指针,害得后者的函数签名(function signature)往往对不上所实际要调用的函数签名。(这一条可以当成一个考虑项而不是必须项)
- 类型转换:
尽量使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式 - 强烈建议你在任何可能的情况下都要使用 const. 此外有时改用 C++11 推出的 constexpr 更好。
- 用户自定义类型也可以定义接收 std::initializer_list 的构造函数和赋值运算符,来实现可以通过初始化列表构造。
- Lambdas, std::functions 和 std::bind 可以搭配成通用回调机制,这一点使用非常多
- 不要使用复杂的模板编程(本菜鸡觉得非常有道理)
六、命名约定(重点)
- 通用命名规则:尽可能给有描述性的命名,别心疼空间,毕竟让代码易于新读者理解很重要。不要用只有项目开发者能理解的缩写,也不要通过砍掉几个字母来缩写单词,例如:
int price_count_reader; // 无缩写
int num_errors; // “num” 本来就很常见
int num_dns_connections; // 人人都知道 “DNS” 是啥
int n; // 莫名其妙。
int nerr; // 怪缩写。
int n_comp_conns; // 怪缩写。
int wgc_connections; // 只有贵团队知道是啥意思。
int pc_reader; // "pc" 有太多可能的解释了。
int cstmr_id; // 有删减若干字母。
- 文件命名:文件名要全部小写, 可以包含下划线 (_) 或连字符 (-),最好使用下划线。不要使用已经存在于 /usr/include 下的文件名,通常应尽量让文件名更加明确。定义类时文件名一般成对出现, 如foo_bar.h 和 foo_bar.cc
- 类型命名:类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum。
- 变量命名:变量名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾,结构体变量不需要下划线结尾。
- 常量命名:在全局或类里的常量名称前加 k,且除去开头的 k 之外每个单词开头字母均大写,例如:kDaysInAWeek
- 函数命名:常规函数的每个单词首字母大写, 没有下划线,取 值 和 设 值 函 数 则 要 求 与 变 量 名 匹 配(get和set函数,例如:set_num_entries)。
- 名字命名空间:用小写字母命名, 并基于项目名称和目录结构: google_awesome_project
- 枚举命名:与常量或者宏命名一致,前面加k或者全部大写,(kEnumName 或是 ENUM_NAME)。
总结:函数命名和类型命名比较像,一般都是首字母全部大写且不使用下划线。常量命名比较特殊,前面加k且后面的首字母全部大写。变量名全部是小写,使用下划线连接。
七、注释(重点)
- 注释风格:使用 // 或 /* */, 统一就好.
- 文件注释: 在每一个文件开头加入版权公告, 然后是文件内容描述。
- 类注释:每个类的定义都要附带一份注释, 描述类的功能和用法
- 函数注释:函数声明处注释描述函数功能; 定义处描述函数实现.
- 变量注释:通常只注释重要的或者是通过名字无法确定用途的变量
- TODO注释:对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释,TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的大名, 邮件地址, 或其它身份标识.
八、代码格式(重点)
我对这部分进了删减,因为现在大部分开发工具都能进行一键格式化代码,所以针对格式,前面的命名规范比较重要。
- 条件语句:
if (condition) {
... // 2 空格缩进。
} else { // else 与 if 的右括号同一行。
...
}
- switch和case语句:
switch (var) {
case 0: { // 2 空格缩进
... // 4 空格缩进
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
- 指针和引用:句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格.
- 命名空间内容不缩进。
九、结束语
风格指南的重点在于提供一个通用的编程规范, 这样大家可以把精力集中在实现内容而不是表现形式上. 我们展示了全局的风格规范, 但局部风格也很重要, 如果你在一个文件中新加的代码和原有代码风格相去甚远, 这就破坏了文件本身的整体美观, 也影响阅读, 所以要尽量避免。