读书笔记(二)---Google C++ 编码风格

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 声明和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和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.隐含类型转换

C++ explicit关键字详解

不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 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版本无效)

检测C++源码风格

参考: cpplint 嵌入VS平台上使用心得

参考:Google代码规范工具Cpplint的使用

在线编程测试

多种语言在线测试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值