




已阅读5页,还剩45页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Google C+ Style Guide谷歌C+编程风格指南 郑州大学 赵峻 (仅供参考)谷歌C+编程风格指南 版本:3.180Benjy WeinbergerCraig SilversteinGregory EitzmannMark MentovaiTashana Landray翻译:郑州大学 赵峻(仅供参考)目录一、背景1二、正文11.头文件(Header Files)11.1#define保护(#include guard)11.2头文件的依赖关系(Header File Dependencies)21.3内联函数(Inline Functions)21.4内联头文件(The inl.h Files)31.5函数参数次序(Function Parameter Ordering)31.6包含的命名和次序(Names and Order of includes)32.作用域(Scoping)42.1名称空间(Namespaces)42.2类嵌套(Nested Classes)72.3外部函数、静态成员函数和全局函数(Nonmember, Static Member, and Global Functions)72.4局部变量(Local Variables)82.5静态变量和全局变量(Static and Global Variables)83.类(Classes)93.1在构造函数中完成工作(Doing Work in Constructors)93.2默认构造函数(Default Constructor)93.3显式构造函数(Explicit Constructors)103.4复制构造函数(Copy Constructos)103.5结构体与类(Structs vs Classes)113.6继承(Inheritance)113.7多重继承(Multiple Inheritance)123.8接口(Interface)123.9运算符重载(Operator Overloading)123.10访问控制(Access Control)133.11声明次序(Declaration Order)133.12定义简短函数(Write Short Functions)144.谷歌经验技巧(Google-Specific Magic)144.1智能指针(Smart Pointers)144.2CPPlint145.其他C+特性(Other C+ Fetures)155.1引用参数(Reference Arguments)155.2函数重载(Function Overloading)155.3默认参数(Default Arguments)165.4可变长度数组和内存申请(Variable-Length Arrays and alloca()165.5友元(Friends)165.6异常处理(Excpetions)175.7运行时类型信息(Run-Time Type Information, RTTI)185.8类型转换(Casting)185.9流(Streams)185.10前置自增和前置自减(Preincrement and Predecrement)195.11const修饰符的使用(Use of const)205.12整型类型(Integer Types)205.1364位兼容性(64-bit Portability)215.14预处理宏(Preprocessor Macros)235.150和空(0 and NULL)235.16存储容量运算符(sizeof)235.17增强库(Boost)245.18C+ 0x库246.命名(Naming)256.1一般命名规则(General Naming Rules)256.2文件命名(File Names)266.3类型命名(Type Names)266.4变量命名(Variable Names)266.5常量命名(Constant Names)276.6函数命名(Function Names):276.7名称空间的命名(Namespace Names)286.8枚举器的命名(Enumerator Names)286.9宏命名(Macro Names)286.10命名规则的例外情况(Exceptions to Naming Rules)297.注释(Comments)297.1注释风格(Comment Style)297.2文件注释(File Comments)297.3类注释(Class Comments)307.4函数注释(Function Comments)307.5变量注释(Variable Comments)317.6以空、真/假、数字作为参数(NULL、true/false、1,2,3)337.7注释的标点、拼写和语法 (Punctuation,Spelling and Gramma)337.8TODO注释(TODO Comments)347.9废弃性注释(Deprecation Comments)348.编码格式(Formatting)348.1行长度(Line Length)348.2非ASCII码字符(Non-ASCII Characters)358.3窗格还是制表符(Spaces vs. Tabs)358.4函数声明与定义(Function Declarations and Definitions)358.5函数调用(Function Calls)378.6条件语句(Conditonals)378.7循环和多分支语句(Loops and Switch Statements)398.8指针与引用表达式(Pointer and Reference Expressions)408.9布尔表达式(Boolean Expressions)408.10返回值(Return Values)418.11变量和数组的初始化(Variable and Array Initialization)418.12预处理指令(Preprocessor Directives)418.13类格式(Class Format)428.14构造函数初始化列表(Constructor Initializer Lists)428.15名称空间格式(Namespace Formatting)438.16水平空白(Horizontal Whitespace)438.17垂直空白(Vertical Whitespace)449.本规则的例外情况(Exceptons to the Rules)459.1现存不一致代码(Existing Non-conformant Code)459.2Windows代码(Windows Code)4510.结束语(Parting Words)453一、 背景C+ 是很多谷歌开源项目的主开发语言。正如每一个C+程序员所知,C+拥有很多强大的特性,但与此同时带也来了很大的复杂性,这就导致C+代码极易出现问题且很难阅读和维护。本指南的目标就是根据大量经验,描述C+编码过程中建议和不提倡的编码规则,以便控制其复杂性。这些规则的运用使你在高效而创造性地使用C+的同时,又能很好是保持代码的可维护性。风格,或者说可读性,也就是我们C+编码的惯例。使用“风格”似乎有些用词不当,毕竟惯例远远不止源文件的格式。保持代码可维护性的一种方法就是强调编码的一致性。程序员能快速查看并理解其他程序员的代码是很重要的。维持一种统一风格并遵照惯例意味着我们可以简单地使用“模式匹配”来推断大量符号的含义和其不变性。建立通用、惯例和模式使代码更容易理解。也许有时有必要改变我们的一贯风格,尽管如此,我们还是尽量保持一致性的好。本指南的另一个论点是C+特性臃肿。C+是一门包含有大量高级特性的巨型语言。某些情况下,我们限制甚至禁止使用某些特性。我们这样做仅仅是想使代码简单并避免由这些特性引起的大量常见错误和问题。本指南将列出这些特性并指出为什么限制它们的使用。谷歌所有开源项目都符合本指南中的要求。注意:本指南不是C+入门指导,我们假设读者对C+已经非常熟悉。二、 正文1. 头文件(Header Files)通常,每个源文件(.cc file)文件都应该有一个与之关联的头文件(.h file)文件。当然,有一些常见例外,比如只有一个main()函数的单元测试和小源文件。正确地使用头文件可以使代码的可读性、体积和性能有一个大的提升。通过下面的规则,你将了解大量使用头文件的缺陷。1.1 #define保护(#include guard)每个头文件都应该有一个#define保护以防止它被多次包含。而且符号的命名最好是以下形式:_H_。尽管保护各不相同,但它们应该以完整的项目资源目录为基础。比如foo项目中的foo/src/bar/baz.h文件应该这样保护:#ifndef FOO_BAR_BAZ_H_#define FOO_BAR_BAZ_H_.#endif / FOO_BAR_BAZ_H_1.2 头文件的依赖关系(Header File Dependencies)当一个前置声明足够时,不要使用#include。当你包含一个头文件时,便引入了头文件依赖关系,每当这个头文件改变时,代码都需要重新编译。而且,如果这些头文件还包含其他头文件,任何改变都将导致包含该头文件的代码重新编译。因此,我们提倡最小化包含,尤其是头文件包含头文件的情形。你可以通过使用前置声明来明显地减少自定义头文件包含其他头文件的数量。比如,如果你的头文件使用File类而不需要知道File类的声明,就可以前置声明File类,而不需要使用#include “file/base/file.h”如何在不访问其定义的情况下使用类Foo呢?1. 声明数据成员Foo* 或者Foo&;2. 声明以Foo为参数,和/或返回值的函数。(有一个例外:如果参数是Foo或者const Foo&,有一个隐式单参数构造函数,这种情况下我们需要引入完全定义来支持自动类型转换)。3. 声明静态的Foo数据成员,这是因为静态数据成员在类定义外定义。另一方面,如果你的类是Foo的子类或者包括一个Foo类型的数据成员,你必须包含该头文件。有时使用指针成员(或使用更好的智能指针)代替对象成员更合理。然而,这会使代码的可读性复杂化并影响性能,所以,当仅以最小化包含文件数为目的时,尽量不要这么做。通常,.cc文件需要知道其所用类的具体实现,因此需要包含一些头文件。注意:如果你在.cc文件中使用Foo标识符,你应该自己定义Foo,要么通过一个#include命令,要么通过一个前置声明。然而有一个例外:如果在myfile.cc中使用Foo,在myfile.h中#include(或者前置声明)Foo也可以。1.3 内联函数(Inline Functions)当函数很小(比如10行或者更少)时才定义函数为内联。内联定义:通过内联,编译器会在调用处将函数展开为代码,而不通过通常的函数调用机制。利:由于内联函数通常很小,所以可产生更高效的目标代码。尽量内联类成员访问和修改函数(getters and setter)和其他一些简短,对性能要求关键的函数。弊:过量使用内联将使程序性能受损。视其规模,内联一个函数即可能使其代码量增加,也可能使其代码量减少。内联一个小的类成员访问函数(getter)通常会减小其代码量,然而内联一个很大的函数则会显著增加其代码量。现代处理器由于使用了指令缓存,在运行小规模代码时将更快。结论:一个好的经验法则是,当一个函数超过10行时,不要使用内联。注意析构函数,它们常常因不明显的成员或者基类析构调用比看起来的规模大。另一个有用的经验法则是,内联一个包含有循环或者开关指令的函数常常是不划算的(除非在极特殊情况才会执行这些循环和开关指令)。知道这一点很重要:一些函数常常不能被内联,即使被声明成这样,比如虚函数和递归函数常常不能被内联。内联一个虚函数的主要原因是将它们的定义放在类定义中,或者在类定义中说明它们的行为,比如类成员访问器和修改器。1.4 内联头文件(The inl.h Files)如有必要,你可以使用-inl前缀来定义复杂的内联函数。应该在头文件中定义内联函数,这样,编译器才能将其代码复制到调用处。然而,实现代码通常应该包含在.cc文件:头文件一般不包含实现代码,除非为了改善可读性或者出于性能的考虑。如果一个内联函数的定义很短,包含很少(如果有的话)的逻辑判断,则应在头文件中实现它们。比如,类成员访问器和修改器应该在类定义中实现。为方便定义和调用,一些复杂的内联函数也可在头文件中定义,当这些函数使头文件变得太过臃肿时,增加一个-inl.h文件来单独定义它们。-inl.h头文件使内联函数的实现和类的定义分开,同时又允许在需要的时候包含其实现。-inl.h的另一个用途是定义函数模板。这将使你的函数模板定义可读性更好。另外,不要忘了,-inl.h文件也是需要#define保护的。1.5 函数参数次序(Function Parameter Ordering)当定义一个函数时,其参数次序应该是:输入、输出。一个C/C+函数的参数无外乎输入、输出或者兼具两者。输入参数常常是数值或者常引用,而输出或者出入参数则是非const指针。定义参数次序时,通常将所有输入参数置于输出参数之前。尤其注意,不要简单在把新参数加在参数列表最后,要将新输入参数置于所有输出参数之前。然而,这不是一个一成不变的规则,比如出入参数(一般是类或结构体)。1.6 包含的命名和次序(Names and Order of includes)为增加可读性并避免隐蔽的依赖关系,请使用标准的包含次序:C库、C+库、其他库头文件、自定义头文件。所有项目的头文件应该按其资源目录降序排列,且不要使用Unix的简略目录表示法(.表示当前目录,.表示父目录)。比如,google-awesome-project/src/base/logging.h应该这样被包含: #include “base/logging.h”如果dir/foo.cc的主要功能是实现和测试dir2/foo2.h中的内容,可以这样安排:1. dir2/foo2.h2. C系统文件3. C+系统文件4. 其他库头文件5. 本项目头文件这种首选次序可以减少隐蔽的依赖关系。我们希望每一个头文件都可以独立编译。最简单的方法就是确保它们在.cc文件中是第一次被包含。dir/foo.cc和dir2/foo2.h通常在一个目录中(比如base/basictypes_test.cc和base/basictypes.h),当然,也可以在不同目录中。各部分内,最好按字母表顺序排列。比如google-awesome-project/src/foo/internal/fooserver.cc可以这样排序包含文件: #include “foo/public/fooserver.h” /首选位置 #include #include #include #include #include “base/basictypes.h” #include “base/commandlineflags.h” #include “foo/public/bar.h”2. 作用域(Scoping)12122.1 名称空间(Namespaces)在.cc文件中,通常鼓励匿名名称空间。当需要命名一个名称空间时,可以基于项目或者它的路径。不要使用using指令。定义:名称空间将全局作用域分成独立且命名的子作用域,因此对于防止命名冲突很有帮助。利:名称空间提供了一条补充由类提供的分层命名的轴线。举个例子,如果两个不同的项目有一个同名的全局类Foo,这些符号在编译或者运行时就有可能冲突。如果把它们的代码放在各自项目的名称空间中,project1:Foo和project2:Foo就是不同的符号,也不会发生冲突了。弊:名称空间可能引起混乱,因为它提供了额外的分层命名轴线以补充由类提供的另一条。在头文件中使用匿名名称空间很容易违反C+的一次定义规则(One Definition Rule, ODR)结论:请按下面的规则使用名称空间:l 匿名名称空间:1. 在C+中,匿名名称空间是允许甚至是被鼓励的:在.cc文件中,为防止运行时命名冲突:namespace / 这通常包含在一个.cc文件中 / 名称空间的内容不应该缩进enumkUnused,kEOF,kError /常见语句bool AtEof()return pos = kEOF;/使用我们的名称空间EOF./ namespace然尔,与特定类相关的文件作用域声明可能在类中被作为类型、静态数据成员或者静态成员函数而不是在匿名名称空间中定义。通常使用注释/ namespace来结束一个匿名空间的定义。2. 不要在头文件中使用匿名名称空间。l 命名名称空间:命名名称空间的使用规则如下:1. 名称空间应该包围#include后的所有代码,包括gflags(一种调试工具,由微软发布,用于检测内存泄漏)定义、声明和来自其他名称空间的前置类声明。 / 位于头文件 namespace mynamespace / 所有的声明都应该在名称空间作用域内 / 注意这里没有缩进 class MyClass public: . void Foo(); ; / namespace mynamespace / 位于.cc文件 Namespace mynamespace / 所有函数的定义位于名称空间的作用域内 void MyClass:Foo() . / namespace mynamespace通常.cc文件可能包括更复杂的代码,比如从其他名称空间引入的类引用。#include “a.h”DEFINE bool(someflag, false,”dummy flag”);class C; / 类C的全局前置声明namespace b. code for b . / 代码应该左对齐/ namespace b2. 不要在std名称空间中声明任何东西,甚至是标准库类的前置声明。在std名称空间中声明名称是未确定行为或者不可移植的。要从标准库中声明名称,包含相应的头文件即可。3. 不可以使用using指令(using-directive)将一个名称空间中的所有名称引入。/ 禁止这有可能引起名称空间冲突using namespace foo;4. 在源文件、头文件中的函数、方法或者类中可以使用using声明(using-declaration)/ 在源文件中允许/ 但在头文件中必须在函数、方法或者类中using :foo:bar;5. 名称空间别名可在源文件中、头文件的全局名称空间中,或者函数和方法中任意使用。/ 源文件中常用名称的受限访问namespace fbz = :foo:bar:baz;/ 头文件中常用名称的受限访问Namespace librarian/ 下面的别名在所有包含此头文件的都可使用(限于librarian / 名称空间)/ 因此,别名应该与项目保持一致性namespace pd_s = :pipeline_diagnostics:sidetable;inline void my_inline_function() / 位于函数或者方法内的名称空间别名 namespace fbz = :foo:bar:baz; ./ namespace librarian注意:头文件中的别名在所有包含它的文件中都可见,所以公共头文件(其他项目可以使用)和那些间接被它们包含的头文件,应该避免定义别名,毕竟,通常的目标是尽量保持公共API简小。2.2 类嵌套(Nested Classes)尽管你可以使用公共的嵌套类来提供接口,但请尽量使用名称空间来保持声明的局部性。定义:类可以在其内部定义另一个类(常称为成员类(Member Class))。class Foo private: / Bar是一个成员类,嵌套定义于Fooclass Bar .利:当嵌套类仅被外部类使用时,这种定义方法很有用,这样可以避免在外部定义类引起的作用域混乱。可以前置声明嵌套类而将其实现放在源文件中来避免其定义出现在外部类的声明中,毕竟嵌套类的定义只与其实现相关。弊:嵌套类仅可被前置声明于外部类内部。这样,任何操纵Foo:Bar*指针的头文件都必须包含Foo类的全部声明。结论:除非嵌套类是接口的一部分(比如一个包括若干供选择方法的类),否则不要将其定义为公共成员。2.3 外部函数、静态成员函数和全局函数(Nonmember, Static Member, and Global Functions)尽量使用名称空间内的外部函数或者静态成员函数,尽量少使用甚至不使用全局函数。利:一些情况下,外部和静态成员函数很有用。将一个外部函数放在一个名称空间内可以防止全局名称空间混乱。弊:外部函数和静态成员函数作为一个新类的成员可能更好,尤其是当它们访问外部资源或者有很大的相关性时。结论:有时,定义一个与对象无关的函数(即外部函数或者静态成员函数)是很有用甚至必要的。外部函数不应该依赖全局变量,而且应该定义在一个名称空间内。与类不同,名称空间在定义函数的同时可以共享共变量,而定义静态成员函数的类则不允许共享其静态数据。共同为某个类提供一项功能而在同一个编译单元内定义的函数可以导致不必要的耦合和连接时相关性,当其他编译单元直接调用时,静态成员函数尤其如此。这时,可以考虑提取出一个新类,或者将这些函数放在不同库中的不同名称空间内。如果必须定义一个只在其源文件内使用的外部函数,可以使用匿名名称空间或者静态链接(比如static int Foo())来限制其作用域。2.4 局部变量(Local Variables)尽量缩小函数内部变量的作用域并在定义它们时执行初始化。虽然C+允许在函数内部任意定义变量,但我们建议尽量缩小其作用域,尽量在需要时才定义。这样方便读者找到变量声明并知道其类型及初始化情况。特别地,初始化应该代替声明和赋值。例如:int i;i = f(); / 糟糕初始化和声明分开int j = g(); / 提倡声明的同时初始化注意:GCC正确地实现了for循环(for(int i = 0; i 10; i +),i的作用域仅限于for循环体内,所以在同一作用域内,你可以重复使用i。if和while语句中的局部变量也一样。比如:while(const char *p = strchr(str,/)str = p+1;警告:如果变量是一个类,它的构造函数将在每次进入作用域时被调用并创建实例,它的析构函数也将在每次退出作用域时被调用。/ 低效的实现for(int i = 0; i 1000000; +i) Foo f; / 这个类的构造函数和析构函数将被调用1000000 f.DoSomething();将一个在循环内使用的变量定义在循环外将更高效:foo f; / 这个类的构造函数和析构函数仅被调用一次for(int i = 0; i 1000000; +i)f.DoSomething();2.5 静态变量和全局变量(Static and Global Variables)类的静态和全局使用是被禁止的:这些变量可能由于不确定的构造和析构次序而引起难于发现的缺陷。有静态存储期间,包括全局变量、静态变量、静态类成员和静态函数变量的类必须是老式平坦数据类型(POD),包括int, char,float,或者指针,或者数组、结构体。在C+中,类对静态成员的构造和初始化仅有部分规定,甚至每次编译都有可能不同,这很容易导致难以察觉的缺陷。因此,除了不允许定义全局类变量外,我们也不允许使用一个函数来初始化一个全局变量,除非这个函数(比如geteny(),getpid())不依赖任何其他全局变量。同样,类的析构顺序恰恰和其构造顺序相反。由于构造顺序尚且不确定,何况析构顺序呢。举个例子,一个程序行将结束的时候,一个静态变量已经被销毁,但代码仍在运行(也许是另一个线程)并试图访问它,但失败了。或者一个静态string变量的析构可能在另一个包含有该变量的引用的变量析构中被执行。因此,只允许仅仅包含POD数据的静态变量。显然vector(代替C数组),或者string(用const char实现 )都不行。如果你确实需要定义一个静态或者全局类变量,考虑从主函数或者pthread_onece()函数初始化一个指针(永远不会被销毁)。注意,这个指针一定是一个普通指针而不是智能指针,因为智能指针的析构将面临析构次序的问题。3. 类(Classes)类是C+代码的基本单位。自然,其使用也是广泛的。这一部分将告诉你在使用类时应该和不应该做的。12333.1 在构造函数中完成工作(Doing Work in Constructors)通常认为构造函数仅仅完成成员变量的初始化。其他复杂的初始化工作则交给Init()函数。定义:可以在构造函数中实现类的初始化。利:形式简单,在使用类时不必担心类是否被初始化。弊:在构造函数中完成初始化工作面临如下问题:1. 由于缺少异常处理(在构造函数中不允许使用),构造函数很难发现错误;2. 如果初始化失败,继续使用类将进入不可预知状态;3. 如果构造函数是虚函数,则其调用不会传至子类的实现。未来对类的修改可能悄悄地引入此问题,甚至类不是其子类时也会引起混乱。4. 如果创建全局类变量(虽然违反此指南,但仍有人这么做),构造函数将在main()执行之前被调用,这很可能打破在构造函数中的假设。譬如,gflags还未被初始化。结论:如果初始化工作对于类很重要,考虑使用Init()方法。特别地,构造函数不应该调用虚函数,这很可能引起错误、访问未初始化的全局变量等问题。3.2 默认构造函数(Default Constructor)如果类定义了成员变量且没有其他构造函数,应定义默认构造函数,这样可以确保新建对象的内部状态一致和有效。否则,编译器将会不安全地初始化类。定义:当创建一个类而不传入参数时,编译器便会调用默认构造函数来完成初始化。比如使用new运算符时总是调用这个构造器。利:按默认方式初始化结构体,能处理非法值,简化调试工作。弊:增加代码写书量。结论:即使你没有定义默认构造函数,编译器也会自动产生一个来初始化新对象,这种构造函数通常不能正确地完成初始化工作。继承自其他类且未增加成员变量的子类不需要再定义默认构造函数。3.3 显式构造函数(Explicit Constructors)关键字explicit用于仅有一个参数的构造函数。定义:通常,只接受一个参数的构造函数可用于类型转换。比如,Foo的构造函数:Foo:Foo(string name),当向以Foo类型为参数的函数传递string参数时,将调用这个构造函数完成string到Foo的转换。有时这很方便,但有时会带来麻烦,比如这种机制在违背你本意的情况下完成类型转换并创建新的对象。声明一个构造函数为显式(explicit)可以避免这种转换。利:避免不希望的类型转换。弊:无。结论:最好在每一个只有一个参数的构造函数前用explicit进行限制。但复制构造函数例外,在一些极罕见的情况下允许转换。还有一种例外情况是,那些打算作为透明封装的类。这两种情况应该以注释注明。3.4 复制构造函数(Copy Constructos)在必要时才提供复制构造函数和赋值运算符。否则,使用DISALLOW_COPY_AND_ASSIGN来禁用它们。定义:复制构造函数和赋值运算符用来创建一个对象的副本。复制构造函数在需要时被自动调用,比如以传值方式传递一个对象时。利:复制构造函数方便对象的复制。C+标准模板库(STL)中的容器内容必须是可复制和可赋值的。复制构造函数比CopyFrom()这种替代方案更高效,因为它将构造和复制进行了结合,某些情况下,编译器会略去它,它也避免了堆分配的开销。弊:C+对象的显式复制常会导致缺陷和引起性能问题。它也会降低代码的可读性,与引用相比,传值将使找出到底是哪个对象被来回传递变得困难,因此,找出对象在何处被修改也变得不可映射。结论:只有少量的类具有可复制性。大多数要么有一个复制构造函数,要么支持赋值运算符。通常,指针和引用起到复制的功能,且性能更好。比如,你可以向函数传递对象的引用或者指针而不是对象本身,你也可以在C+ STL标准容器中保存对象的指针。如果类需要复制性,最好提供复制方法,比如CopyFrom()或者Clone()而不要使用不能被显式调用的复制构造函数。如果复制方法不满足要求(比如出去性能的要求或者类需要被保存在STL标准容器中),可以提供复制构造函数和赋值运算符。如果你的类不需要复制构造函数或者赋值运算符,必须显式地禁用它们。将它们在类的private部分声明,且不提供任何相关定义(这样,任何试图调用都将导致连接错误)。方便起见,可以使用DISALLOW_COPY_AND_ASSIGN宏:/ 定义一个宏来禁用复制构造函数和赋值运算符/ 这两个方法的声明应该位于类声明的私有部分#define DISALLOW_COPY_AND_ASSIGN(TypeName) TypeName(const TypeName&); void operator=(const TypeName&)Foo f; 在Foo类中这样声明:class Foopublic:Foo(int f);Foo(); private: DISALLOW_COPY_AND_ASSIGN(Foo);3.5 结构体与类(Structs vs Classes)当对象只是用来保存数据时,则使用结构体,其他情况使用类。在C+中,struct和class几乎是同义词。我们将给它们加上自己的语议,以便于正确地使用它们进行数据定义。结构体应该被用来承载数据,也可包含相关的常量,除了数据成员的访问/修改方法外不提供其他方法。而且数据成员的访问/修改也是直接对其数据的访问而不通过方法调用。即使它有其他方法,这些方法也只完成数据成员的修改,比如构造器、析构函数,Initialize()、Reset()、Validate()。如果还需要其他方法,则使用类。为保持与STL的一致性,函数对象(functor)和类型获取器(traits)可以使用结构体。注意:在结构体和类中,成员变量的命名方式是不同的。3.6 继承(Inheritance)组合通常比继承更合适。使用继承时,一般为公有继承。定义:一个子类将继承基类的全部数据和操作。特别地,C+中,继承有两种主要方式:实现继承:实质性代码都被子类继承和接口继承:子类只继承接口名称。利:实现继承通过重用基类的代码来减小程序规模。由于继承是一个编译时的声明,程序员和编译器可以理解这些操作并检测错误。接口继承则通过编程使一个类对外暴露特定的API。同样,当一个类没有定义必要的API时,编译器可以检测出错误。弊:对于实现继承,由于子类的实现代码需要在基类和其自身展开,理解这些实现将变得困难。子类不可以覆盖非虚方法,所以它不能改变(基类的)实现。基类也可以定义一些数据成员来规定其物理布局。结论:所有继承应该是公有继承。如果使用私有继承,更好的方法是使用一个基类的实例作为成员。不要滥用实现继承,类组合常常更合适。只有当Bar有充分的理由说明其is a Foo时,才能说Bar是Foo的子类必要时使析构函数虚化。任何定义了虚函数的类,析构函数都应该被虚化。子类需要使用的基类方法最好用protected加以限制。注意,基本的数据成员必要是private。重写继承的虚函数时,在子类中显式地声明其为virtual。一旦漏掉了virtual,读者必须检测其所有基类来确保它是不是虚函数。3.7 多重继承(Multiple Inheritance)多重实现继承通常罕有其用。只有当仅一个基类被实现继承,而其他类都是纯接口且以Interface作为后缀声明时才允许多重继承。定义:多重继承允许一个类继承多个基类。请注意区别基类、纯接口和实现接口。利:多重继承允许你更大限度地重用代码。弊:多重继承只有一种情况才被允许:除了第一基类,其他类都是以Interface作为后缀结束的纯接口。注意:在Windows中有一个例外。3.8 接口(Interface)作为接口的类可以但不必以Interface后缀结束。定义:满足以下条件的类被称为纯接口:l 只有公共的纯虚函数(“=0”)和静态方法(见下文,析构函数例外);l 只能有静态数据成员;l 没有构造函数定义。即使有构造函数,也仅是默认构造函数且被声明为protected;l 如果是子类,只能继承自满足以上条件且以Interface后缀结束的类;由于内部都是纯虚函数,接口类不能被直接实例化。为使所有接口的实现都可以被正确地销毁,所有接口类必须定义一个虚析构函数(这与第一条冲突)。详细请参见Stroustup的The C+ Programming Language第3版的12.4章节。利:最好给纯接口类加上Interface后缀以让其他程序员知道此类不能添加任何方法实现和非静态数据成员,这对于多重继承来说很重要。Java程序员可能更了解接口。弊:Interface后缀使类名变得冗长而难以阅读和理解。而且,接口的特征可能被误解为其具体实现不能暴露给调用者。结论:满足以上条件的类最好以Interface后缀结束。然而,这不是必须的。3.9 运算符重载(Operator Overloading)只有在很罕见的情况下才会用到运算符重载。定义:类可以重载诸如+/-的运算符以使其能像内建类型一样操作。利:这些类可以像内建类型一样操作(比如int),代码看上去更直观。相比于那些呆板的命名函数(比如Equals()、Add()),重载的运算符是一种操作性的命名。为确保某些模板函数的正确性,有时必须重载运算符。弊:运算符有很多弊端:l 它可能使我们误以为大开销的操作(运算符重载实际上是函数调用)是小开销的内建操作;l 找到重载运算符的调用点常常很困难。比如找到函数Equals()的调用处比找到=的简单的多。l 一些运算符也适用于指针,这很容易造成程序缺陷。举个例子:&Foo+4和Foo+4实现的操作完全不同,但编译器不会报错。运算符重载也有可能造成歧义。比如,如果一个类重载了单目运算符&,它不可以被安全地前置声明。结论:一般情况下不要重载运算符。尤其赋值运算符,通常很隐蔽。如果需要,可以定义Equals()和CopyFrom()等函数。如果一个类需要被前置声明,一定避免危险的单目运算符&的重载。然而,在很罕见的情况下,你可能需要重载运算符来与模板和C+标准类(如(ostream&,const T&)互操作。合理的情况下,可以使用运算符重载,但如果可能,还是应该避免。尤其注意,不要仅仅为了类能在标准容器中作为键而重载=和,相反,你应该创建相等和比较函数对象。一些标准模板库算法可能需要你重载=,但必须说明原因。参见复制构造函数和函数重写。3.10 访问控制(Access Control)数据成员应该被定义成私有(private)(静态常数据成员除外),需要时提供访问器(accessor)(出于技术考虑,使用Google Test时,承载测试功能的类可以将其数据成员声明成protected)。通常,名称为foo_的变量其访问函数为foo(),而其修改器(mutator)则为set_foo()。访问器常在头文件中定义为内联函数。参见继承和函数命名。3.11 声明次序(Declaration Order)请按下面的规则次序来定义类:公共成员位于私有成员前;方法位于数据成员前(变量)等等。公共部分位于保护部分前,保护部分位于私有部分前,如果某个部分空,则忽略它。在每个部分,请按照下面的次序来声明成员:l 类型定义(Typedefs)和枚举(Enums);l 常量(static const数据成员);l 构造函数;l 析构函数;l 类方法,包括静态方法l 数据成员(静态常量除外);友元声明和DISALLOW_COPY_AND_ASSIGN宏调用应该在私有部分。私有部分应该在类定义的最后部分。参见复制构造函数。在相关源文件中,方法的实现次序也应尽量与类声明中一致。不要将把大函数内联定义在类定义中。通常,只有很短且性能要求高的情况下才将一个函数定义成内联。参见内联函数。3.12 定义简短函数(Write Short Functions)函数应该尽量简短并功能单一。不得不承认,某些场合长函数很合适,所以不太容易去限制其长度。如果一个函数超过40行,考虑可否在不改变程序结构的情况下将其拆分。尽管长函数目前工作良好,也许其他程序员日后会修改并给它添加新功能。这会导致难以发现的缺陷。保持你的函数简短可以方便其他程序员阅读和修改你的代码。阅读一些代码时,你可能发现长函数。不过,不要害怕修改它们。如果发现使用它们很困难或者调试有难度,或者你想在多处使用部分函数代码,考虑把它拆分成更易管理的片段。4. 谷歌经验技巧(Google-Specific Magic)谷歌采用很多技巧和工具来确保C+代码的健壮性,而且,谷歌使用C+的方式和其他地方很不同。12344.1 智能指针(Smart Pointers)如果使用指针,最好使用受限指针(soped_ptr)。而std:tr1:shared_prt只有在很少的情况下都会用得到,比如对象需要被标准模板库容器包含。任何情况下不要使用自动指针(auto_ptr)。智能指针是指行为类似于指针但增加额外功能的对象。当一个scoped_ptr被销毁时,它也将删除其指向的对象删除。shared_prt也具有类似功能,但它会实现引用计数直到它指向的最后一个对象删除它。通俗点说,我们希望定义清楚每个对象的归属。但最清楚的对象归属是此对象被域或者局部变量拥有,而不是使用指针。另一个极端是,在它们被定义时,引用计数指针不被任何对象拥有。这种定义的问题是,这样将很容易循环引用或者对象无法被销毁的奇怪现象。而每个原子操作都进行复制或赋值将影响性能。即使不提倡,引用计数指针有时却是最简单和幽雅的问题解决方式。4.2 CPPlint使用cpplint.py来检测风格错误。Cpplint.py是一个能读取源文件并识别风格错误的工具。尽管不很完美,有很多优点和缺点,但它仍是一个有用的工具。主动错误信息可以将/ NOLINT放在行后来忽略。有些项目带有如何从项目工具运行cpplint.py的说明。如果没有,你可以单独下载它。5. 其他C+特性(Other C+ Fetures)123455.1 引用参数(Reference Arguments)所有用引用传值的变量应该被const修饰。定义:在C语言中,如果函数需要修改一个变量,必须使用指针作为其参数。比如int foo(int*pval)。但在C+中,有了另一种方式,即引用:int foo(int &val)。利:把一个参数定义为引用可以避免丑陋的代码(比如*pval+)。有些程序需要,比如复制构造函数。使程序更明确,不像指针能取得NULL值。弊:由于引用兼具值表达式和指针的主义,会引起迷惑。结论:所有函数引用参数都应该定义为const引用。void Foo(const string &in, string *out)实际上,将值或者常引用作为输入参数而将指针作为输出参数是谷歌的一个惯例。输入参数也可以是常指针,但不允许非const(no
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025合同审批管理程序
- 2025年家用电器租赁合同
- 2025年麻醉科术前准备流程规范化考核模拟题答案及解析
- 高中数学 1.2 空间向量基本定理说课稿 新人教A版选择性必修第一册
- 2025年药理学药物使用指导模拟测试答案及解析
- 2025政府与民间资本合作PPP项目合同指南
- 2024年湖南大众传媒职业技术学院招聘真题
- 2025年急诊抢救流程及操作要点考察答案及解析
- 2025年眼科眼部疾病诊疗技术应用考察答案及解析
- 2025住建局商品房预售合同备案办理服务指南
- 预防校园欺凌-共创和谐校园-模拟法庭剧本
- 《人间词话》十则公开课
- 质量管理学课件第1章
- 磁刺激仪技术参数
- 水泵房设备的保养与维护方案
- 通用机场建设审批程序
- 城市雕塑工程工程量清单计价定额
- 道路保通专项方案
- ansys的讲义ANSYS有限元分析培训
- 腭裂术后语音训练ppt课件
- 数据、信息与知识PPT课件
评论
0/150
提交评论