五、Google C++ 风格指南

一、头文件

        1.如果 .h 文件声明了一个模板或内联函数,同时也在该文件加以定义。凡是有用到这些的 .cpp 文件,就得统统包含该头文件,否则程序可能会在构建中链接失败。不要把这些定义放到分离的 -.h 文件里(译者注:过去该规范曾提倡把定义放到 -inl.h 里过)。

        有个例外:如果某函数模板为所有相关模板参数显式实例化,或本身就是某类的一个私有成员,那么它就只能定义在实例化该模板的 .cpp文件里。

        2.头文件都应该用#define声明其唯一性,格式如下:____H_;为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径. 例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:

#ifndef _FOO_BAR_BAZ_H_

#define _FOO_BAR_BAZ_H_

...

#endif // _FOO_BAR_BAZ_H_

        3.尽可能避免使用前置声明,使用#include包含需要的头文件即可。前置声明的类,是不完全类型,不能定义其对象,只能定义其引用或指针,或以其为形参的函数,或以其为返回值的函数。

        4.只有当函数少于等于10行时,才将其定义为内联函数。递归的函数,循环的函数(或含有switch语句的函数),虚函数(因为虚函数的调用是在运行时,而不是编译时),通过函数指针调用的函数都不能定义为内联的。内联函数定义必须放在 .h 文件中. 如果内联函数比较短, 就直接将实现也放在 .h 中.

        5..h头文件的包含顺序:

        1).相关头文件(放在最前面是因为,当它内遗漏某些必要的库时,本cpp的构建会立刻终止)

        2).C库

        3).C++库

        4).其他库的.h文件

        5).本项目内的.h文件

        6).通过条件编译的.h文件

        例如在fooserver.cpp文件中头文件的顺序应该如下:

#include "foo/public/fooserver.h" //相关头文件,优先位置;后面注意空行,用于区分不同类型的头文件

#include //C库

#include

#include //C++库

#include

#include "base/basictypes.h"//其他库的.h文件

#include "base/commandlineflags.h"

#include "foo/public/bar.h"//本项目内的.h文件

#ifdef LANG_CXX11

#include //通过条件编译的.h文件

#endif  // LANG_CXX11

二、作用域

        1.鼓励在cpp文件内使用匿名命名空间或static声明。使用具名的命名空间时,其名称可基于项目名或相对路径。禁止使用using指示(即将整个命名空间下的东西可见,建议使用using声明:只可见某一句)。禁止使用内联命名空间。

        2.在命名空间的最后,在宏定义的最后,都应该加上注释,表示的是哪个命名空间以及宏等。

        3.头文件,类的前置声明,#define等的不应该包含在命名空间中,其他的都应该包含在内部。

        4.在cpp文件中定义一个不需要被外部引用的变量时,可以使用匿名命名空间或用static修饰,但不要在h头文件中这么定义。

        5.尽量不要使用裸的全局函数,而应使用命名空间内的非成员函数或static修饰。

        6.不要在循环体内犯重复构造和析构的低级错误。

// 低效的实现

for (int i = 0; i < 1000000; ++i)

{

    Foo f;                  // 构造函数和析构函数分别调用 1000000 次!

    f.DoSomething(i);

}

//可以改为如下形式:

Foo f;                      // 构造函数和析构函数只调用 1 次

for (int i = 0; i < 1000000; ++i)

{

    f.DoSomething(i);

}

三、类

        1.构造函数中不应该调用虚函数。

        2.不要使用隐式类型转换,当转换运算符和单参数构造函数请使用explicit关键字。

        3.如果你的类型需要,请自定义拷贝/移动操作,否则把隐式生成的拷贝和移动函数禁用掉。即当明确不需要拷贝/移动操作时,请明确禁用掉,和Effective C++中的说法类似。

        4.当只有数据成员时,请使用struct,其他一概使用class。

        5.使用组合往往比使用继承更合适,如果要使用继承的时候,请使用public继承。不是public继承的,请使用组合。如果你的类含有虚函数,则析构函数必须是虚函数。对重载的虚函数请使用override,final或virtual修饰。尤其是override。

        6.很少用到多重继承,除非,只有一个基类是非抽象类,其他基类都是抽象类,并这些抽象基类都是接口类。只有当所有父类除第一个外都是纯接口类时, 才允许使用多重继承. 为确保它们是纯接口, 这些类必须以Interface 为后缀.

        7.怎么定义一个接口类

        1).只有纯虚函数 (”=0”) 和静态函数 (除了下文提到的析构函数).

        2).没有非静态数据成员.

        3).没有定义任何构造函数. 如果有, 也不能带有参数, 并且必须为 protected.

        4).如果它是一个子类, 也只能从满足上述条件并以 Interface 为后缀的类继承.

        8.除少数特定环境外,不要重载运算符,也不要创建用户定义字面量。

        9.所有数据成员请声明为private的。

        10.相似的声明放在一起,public部分放在最前面,protected其次,private最后。在各个部分中, 建议将类似的声明放在一起, 并且建议以如下的顺序:

        1).类型 (包括 typedef, using 和嵌套的结构体与类),

        2).常量,

        3).工厂函数,

        4).构造函数,

        5).赋值运算符,

        6).析构函数,

        7).其它函数,

        8).数据成员.

        9).友元的声明.

四、函数

        1.函数参数尽量以const引用传递,尽量返回值,其次引用,最后指针;

        2.如果形参中存在输出参数,则应将所有输入参数放在输出参数前;

        3.函数尽量不超过40行。

        4.如果是引用传递的参数,必须加上const。

        5.形参含有输出参数,google的硬性规定:输入参数是值或const引用,输出参数(注意不是返回值)是指针。

void Foo(const string &in, string *out);

        6.对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作。虚函数通常是动态绑定的,但参数是静态绑定的,即运行时根据对象的动态类型执行,但参数是根据静态类型的参数执行。

class Base

{

public:

virtual void mfTest(int a = 10)

{

std::cout << "Base " << a << std::endl;

}

};

class Derived : public Base

{

public:

virtual void mfTest(int a = 20)

{

std::cout << "Derived " << a << std::endl;

}

};

int main()

{

Base *baseObj = new Derived;

baseObj->mfTest();

delete baseObj;

return 0;

}

        7.少用后置类型语法。

六、其他C++特性

        1.只在定义移动构造函数与移动赋值操作时使用右值引用, 不要使用 std::forward 功能函数. 你可能会使用 std::move 来表示将值从一个对象移动而不是复制到另一个对象.

        2.我们不允许使用缺省函数参数,少数极端情况除外(如下),尽可能使用函数重载。调用函数的代码表面上省去了不少参数,但编译器在编译时还是会在每一个调用代码里补上所有默认实参信息,造成大量的重复。

        1).位于cpp文件里的静态函数或匿名空间函数,毕竟都只能在局部文件里调用该函数;

        2).可以在构造函数里缺省参数,毕竟不可能取得他们的地址。缺省参数会干扰函数指针,害得后者的函数签名(function signature)往往对不上所实际要调用的函数签名。即在一个现有函数添加缺省参数,就会改变它的类型,那么调用其地址的代码可能会出错,不过函数重载就没这问题了。

        3).可以用来模拟变长数组。

// 通过空 AlphaNum 以支持四个形参

string StrCat(const AlphaNum &a,

              const AlphaNum &b = gEmptyAlphaNum,

              const AlphaNum &c = gEmptyAlphaNum,

              const AlphaNum &d = gEmptyAlphaNum);

        3.我们不允许使用变长数组和alloca(),改用std::vector 或 std::unique_ptr.

        4.我们不使用 C++ 异常.

        5.我们禁止使用 RTTI,运行时类型识别。typeid,dynamic_cast。如果能够保证给定的基类实例实际上都是某个派生类的实例,那么就可以自由的使用dynamic_cast。

dynamic_cast(指针):对指针的动态类型转化,如果不成功则返回null

dynamic_cast(引用):对引用的动态类型转化,如果不成功则抛出bad_cast的异常。

        6.使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式;

        7.对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符。对简单数值 (非对象), 两种都无所谓. 对迭代器和模板类型, 使用前置自增 (自减).

        8.在任何可能的情况下尽量使用const修饰

Const int *a;//底层const,表示a所指向的东西不可修改,即*a不能被赋值;

Int *const a;//顶层const,表示a本身不可修改,而指向的东西可以修改,即不能给a赋值,而*a可以赋值。

        9.常规定义,short是16位,int是32位,long是32位,long long是64位。

        10.C++中少用#define宏(尽量用const,inline等替换),如果非要用的话,请注意:

        1).不要在 .h 文件中定义宏.

        2).在马上要使用时才进行 #define, 使用后要立即 #undef.

        3).不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;

        4).不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.

        5).不要用 ## 处理函数,类和变量的名字。

        11.整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 '\0'.

        12.auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。auto 还可以和 C++11 特性「尾置返回类型(trailing return type)」一起用,不过后者只能用在 lambda 表达式里。

        13.适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。

        1).禁用默认捕获,捕获都要显式写出来。打比方,比起 [=](int x) {return x + n;}, 您该写成 [n](int x) {return x + n;} 才对,这样读者也好一眼看出 n 是被捕获的值。

        2).匿名函数始终要简短,如果函数体超过了五行,那么还不如起名(acgtyrant 注:即把 lambda 表达式赋值给对象),或改用函数。

        3).如果可读性更好,就显式写出 lambd 的尾置返回类型,就像auto.

        14.不要使用复杂的模板编程,应该在实现上用模板,而在对外开放的接口上不使用模板。

        15.c++11的一些特性最好不要用,如:

        1).尾置返回类型,比如用 auto foo() -> int 代替 int foo(). 为了兼容于现有代码的声明风格。

        2).编译时合数 , 因为它涉及一个重模板的接口风格。

        3). 头文件,因为编译器尚不支持。

        4).默认 lambda 捕获。

        16.friend 实际上只对函数/类赋予了对其所在类的访问权限,并不是有效的声明语句。所以除了在头文件类内部写 friend 函数/类,还要在类作用域之外正式地声明一遍,最后在对应的 .cpp 文件加以定义。

        17.本风格指南都强调了「友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类」。那么可以把其声明放在类声明所在的头文件,定义也放在类定义所在的文件

七、命名约定

        1.少用缩写;

        2.文件命名:全部小写,单词之间使用_或-隔离,建议使用_;

        3.类型命名:每个单词首字母均大写,不包含_;类,结构体,typedef,枚举量,类型模板参数;

        4.变量命名:包括函数参数,和数据成员名,一律小写,单词之间用_连接,类的成员变量以下为规则。

1).lhs,rhs:常作为变量;

2).p打头:常作为指针;

3).r打头:常作为引用;

4).mf打头:常作为成员函数

5).mv_打头:常作为成员变量

6).mvp_打头:常作为成员指针变量

7).mvr_打头:常作为成员引用变量

        5.常量命名:以const或constexpr声明的变量,都应该以k打头,后面的内容采用单词首字母大写的方式。

        6.函数命名:常规函数使用首字母大写,set和get与变量名匹配(如:set_my_exciting_member_variable())

        7.命名空间:全部小写,以_分割,顶级命名空间应该是项目名,命名空间中的代码,应该放于和命名空间名字相匹配的文件夹或其子文件夹中。

        8.枚举量的命名应该和常量或宏一致:kEnumName 或是 ENUM_NAME(这样的不好,因为经常会和宏定义冲突),建议使用常量,如下:

enum UrlTableErrors

{

    kOK = 0,

    kErrorOutOfMemory,

    kErrorMalformedInput,

};

        9.总结:

        1).少用缩写

        2).首字母大写:类名,类型名,函数名,枚举量(k打头),常量名(k打头)

        3).全部小写,以_分割:文件名,文件夹名,变量名(类的成员变量以m打头),命名空间

        4).全部大写,以_分割:宏定义

八、注释

        1.应该增加文件注释:描述该文件的内容,作者,时间

        2.类应该增加注释:描述功能和用法

        3.函数应该加注释:描述以下内容

        1).函数的输入输出.

        2).对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.

        3).函数是否分配了必须由调用者释放的空间.

        4).参数是否可以为空指针.

        5).是否存在函数使用上的性能隐患.

        6).如果函数是可重入的, 其同步前提是什么?

九、格式

        1.一行的长度不应超过80列;

        2.不适用非ASCII字符;

        3.空格时,要使用空格,而不是制表位;

        4.函数声明和定义

        1).使用好的参数名.

        2).只有在参数未被使用或者其用途非常明显时, 才能省略参数名.

        3).如果返回类型和函数名在一行放不下, 分行.

        4).如果返回类型与函数声明或定义分行了, 不要缩进.

        5).左圆括号总是和函数名在同一行.

        6).函数名和左圆括号间永远没有空格.

        7).圆括号与参数间没有空格.

        8).左大括号总在最后一个参数同一行的末尾处, 不另起新行.

        9).右大括号总是单独位于函数最后一行, 或者与左大括号同一行.

        10).右圆括号和左大括号间总是有一个空格.

        11).所有形参应尽可能对齐.

        12).缺省缩进为 2 个空格.

        13).换行后的参数保持 4 个空格的缩进.

你可能感兴趣的:(语言篇,#,C++语言,c++)