C/C++代码规范

前言

本文摘录自谷歌开源项目开源指南。

详见 —>谷歌开源项目开源指南

头文件

每通常一个 .c/.cpp文件都有一个对应的 .h 文件。也有一些常见例外,如单元测试代码和只包含 main() 函数的 .c/.cpp文件。正确使用头文件可令代码在可读性,文件大小和性能上大为改观。

下面的规则将引导你规避使用头文件时的各种陷阱。

自包含的头文件

头文件应该能够自给自足(自包含的,也就是可以作为第一个头文件被引入),以 .h 结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以以应 .inc 结尾不允许。出分离 -inl.h 头文件的做法。所有头文件要能够自给自足。换言之,用户和重构工具不需要为特别场合而包含额外的头文件。详言之,一个头文件要有 1.2。#define保护,统计包含它所需要的其它头文件,也不要求定义任何特别的符号。

不过有一个例外,即一个文件并不是自足的,而是作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(特定于平台)扩展部分,这些文件就要用 .inc 文件扩展名。

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

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

#define保护

所有头文件都应该使用 #define 来防止头文件被多重包含,命名格式当是: H
为保证唯一性,头文件的命名应该基于所有项目源代码树的全路径。例如,项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_


#endif // FOO_BAR_BAZ_H_

前置声明

尽可能地避免使用前置声明。使用 #include 所有游戏需要的头文件即可。
定义:
所谓「前置声明」(forward declaration)是类,函数和模板的纯粹声明,没伴随着其定义。

优点:

前置声明能够节省编译时间,的多余 #include 会迫使compile-器展开更多的文件,处理更多的输入。
前置声明能够节省不必要的重新编译的时间。 #include 使代码因为头文件中无关的改动而被重新编译多次。

缺点:

前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其API。例如扩大形参类型,加个自带默认参数的模板形参等等。
前置声明来自命名空间 std:: 的符号时,其行为未定义。
很难判断什么时候该用前置声明,时候什么用该 #include 极端情况下,用前置声明代替。 includes 甚至都会暗暗地改变代码的含义:

// bh:
struct B {};
struct D : B {}

// good_user.cpp:
#包括 “BH”
void f (B * );
void f (void * );
void test (D * x ) { f (x ); }

//调用f(B
如果 #include 被 B 状语从句: D 的前置声明替代, test() 就会调用 f(void
) 。
前置声明了include 不少来自头文件的符号时,就会比单单一行的 冗长。
仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂。

结论:

尽量避免前置声明那些定义在其他项目中的实体。

函数:总是使用 #include。

类模板:优先使用 #include。

至于什么时候包含头文件,参见 **1.5。#include的路径及顺序 **。

内联函数

只有当函数只有10行甚至更少时才将其定义为内联函数。
定义:当函数被声明为内联函数之后,编译器会将其内联展开,而不是按通常的函数调用机制进行调用。

优点:

只要内联的函数体小小,内联该函数可以令目标代码更加高效。对于存取函数以及其它函数体比较短,性​​能关键的函数,鼓励使用内联。

缺点:

滥用内联将导致程序变得更慢。内联可能使目标代码量或增或减,这取决于内联函数的大小。内联非常短小的存取函数通常会减少代码大小,但内联一个相当大的函数将戏剧性的增加代码大小。现代处理器由于更好的利用了指令缓存,小巧的代码往往执行更快。

结论:

一个较为合理的经验准则是,不要内联超过10行的函数。谨谨对待析构函数,析构函数往往比其表面看起来要更长,因为有隐含的成员和基类析构函数被调用!

另一个实用的经验准则:内联那些包含循环或 switch 语句的函数常常是得不偿失(除非在大多数情况下,这些循环或 switch 语句从不被执行)。

有些函数即使声明为内联的也不一定会被编译器内联,这点很重要; 比如虚函数和递归函数就不会被正常内联。通常,递归函数不应该声明成内联函数。(YuleFox注:递归调用堆栈的展开并不像循环那么简单,比如递进层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。虚函数内联的主要原因是想把它的函数体放在类定义内,为了图个方便,抑或是当作文件描述其行为,比如精短的存取函数。

#include 的路径及顺序

使用标准的头文件包含顺序可增强可读性,避免隐藏依赖:相关头文件,C库,C ++库,其他库的 .h,本项目内的 .h。
项目内部文件应按照项目源代码目录树结构排列,避免使用UNIX特殊的快捷目录 .(当前目录)或 .. (上级目录)。

例如, google-awesome-project/src/base/logging.h 应该按如下方式包含:#include “base / logging.h”
又如, dir/foo.cpp 或 dir/foo_test.cpp 的主要作用英文的英文实现或测试 dir2/foo2.h 的功能, foo.cpp 中包含头文件的次序如下:dir2/foo2.h (优先位置,详情如下)
C系统文件
C ++系统文件
库其他的 .h 文件
项目本。内 .h 文件
优先这种顺序的排序保证当 dir2/foo2.h 遗漏某些必要的库时, dir/foo.cpp 或 dir/foo_test.cpp 的构建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。

dir/foo.cpp 和 dir2/foo2.h 通常位于同一目录下(如base/basictypes_unittest.cpp 和 base/basictypes.h),但也可放在不同目录下。按字母顺序分别对每种类型的头文件进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。

您所依赖的符号(符号)被哪些头文件所定义,您就应该包含(包括)哪些头文件,前置声明 (向前声明)情况除外。您比如要用到 bar.h 中的某个符号,哪怕您所包含的 foo.h 已经包含了 bar.h,也照样得包含 bar.h,除非foo.h 有明确 说明它会自动向您提供 bar.h 中符号。不过,凡是cc文件所对应的「相关头文件」已经包含的,就不用再重复包含进其cc文件里面了,就像 foo.cpp 只包含 foo.h就够了,不用再管后者所包含的其它内容。

函数

参数顺序

总述:函数的参数顺序为:输入参数在先,后跟输出参数。

说明

C / C ++中的函数参数或者是函数的输入,或者是函数的输出,或兼而有之。输入参数通常是值参或 const 引用,输出参数或输入/输出参数则一般为非 const 指针。在排列参数顺序时,将所有的输入参数置于输出参数之前。特别要注意,在加入新参数时不要因为它们是新参数就置于参数列表最后,而是仍然要按照前述的规则,即将新的输入参数也置于输出参数之前。

这并非一个硬性规定。输入/输出参数(通常是类或结构体)让这个问题变得复杂。并且,有时候为了其他函数保持一致,你可能不得不不所有变通。

编写简短函数

总述:我们倾向于编写简短,凝练的函数。

说明

我们承认长函数有时是合理的,因此并不硬限制函数的长度。如果函数超过40行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。

即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的错误。使函数尽量简短,以便于他在他人阅读和修改代码。

在处理代码时,你可能会发现复杂的长函数。不要害怕修改现有代码:如果证实这些代码使用/调试起来很困难,或者你只需​​要使用其中的一小段代码,考虑将其分割为更加简短并易于管理的若干函数。

引用参数

总述:所有按引用传递的参数必须加上 const。

说明

》在C语言中,如果函数需要修改变量的值,参数必须为指针,如 。在C ++中,函数还可以声明为引用参数: 。int foo(int *pval)int foo(int &val)

优点

引用定义参数可以防止出现 (*pval)++ 这样丑陋的代码。引用参数对于拷贝构造函数这样的应用也是必需的。同时也更明确地不接受空指针。

缺点

容易引起误解,因为引用在语法上是值变量却拥有指针的语义。

结论

函数参数列表中,所有引用参数都必须是 const:void Foo (const string &in , string * out );
事实上这在Google Code是一个硬性约定:输入参数是值参或 const 引用,输出参数为指针。输入参数可以是 const 指针,但决不能是非 const 引用参数,除非特殊要求,比如 swap()。
有时候,在输入形参中用针指更明智。

比如:const Tconst T&可能会传递空指针。
函数要把指针或对地址的引用赋值给输入形参。
总而言之,大多时候输入形参往往是 。用若 则说明输入侧另有处理。所以若要使用 ,则应给出相应的理由,否则会使读者感到迷惑。const T&const T
const T*

函数重载

总述

》若要使用函数重载,则必须能让读者一看调用点就胸有成竹,而不用花心思猜测调用的重载函数到底是哪一种。这一规则也适用于构造函数。

定义

你可以编写一个参数类型的函数,然后用另一个参数类型函数对其进行重载:

const string&const char* class  MyClass  {
   public :
   void  Analyze (const  string  &text );
   void  分析(const  char  * text , size_t  textlen );
};

优点

通过重载参数不同的同名函数,可以令代码更直观。模板化代码需要重载,这同时也能为使用者带来便利。

缺点

如果函数单靠不同的参数类型而重载(acgtyrant注:这意味着参数数量不变),读者就得十分熟悉C ++五花八门的匹配规则,以了解匹配过程具体到底如何。另外,如果派生类只重载了某个函数的部分变体,继承语义就容易令人困惑。

结论

如果打算重载一个函数,可以试试改在函数名里加参数信息。例如,用 AppendString()和 AppendInt() 等,而不是一口气重载多个 Append()。如果重载函数的目的是为了支持不同数量的同一类型参数,则优先考虑使用 std::vector 以便使用者可以用 列表初始化指定参数。

缺省参数

总述

只允许在非虚函数中使用缺省参数,且必须保证缺省参数的值始终一致。参数缺省与 函数重载 遵循同样的规则。一般情况下建议使用函数重载,尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下。

优点

有些函数一般情况下使用默认参数,但有时需要又使用非默认的参数。缺省参数为这样的情形提供了便利,使程序员不需要为了极少的例外情况编写大量的函数。和函数重载相比,缺省参数的语法更简洁明了,减少了大量的样板代码,也更好地区别了“必要参数”和“可选参数”。

缺点

  • 缺省参数实际上是函数重载语义的另一种实现方式,因此所有 不应当使用函数重载的理由 也都适用于缺省参数。
  • 虚函数调用的缺省参数取决于目标对象的静态类型,此时无法保证给定函数的所有重载声明的都是同样的缺省参数。
  • 缺省参数是在每个调用点都要进行重新求值的,这会造成生成的代码迅速膨胀。作为读者,一般来说也更希望缺省的参数在声明时就已经被固定了,而不是在每次调用时都可能会有不同的取值。
  • 缺省参数会干扰函数指针,导致函数签名与调用点的签名不一致。而函数重载不会导致这样的问题。

结论

对于虚函数,不允许使用缺省参数,因为在虚函数中缺省参数不一定能正常工作。如果在每个调用点缺省参数的值都有可能不同,在这种情况下缺省函数也不允许使用。(例如,不要写像 这样的代码。)void f(int n = counter++);

在其他情况下,如果缺省参数对可读性的提升远远超过了以上提及的缺点的话,可以使用缺省参数。如果仍有疑惑,就使用函数重载。

函数返回类型后置语法

总述

只有在常规写法(返回类型前置)不便于书写或不便于阅读时使用返回类型后置语法。

定义

C ++现在允许两种不同的函数声明方式。以往的写法是将返回类型置于函数名之前。例如:

int  foo (int  x );

C ++ 11引入了这一新的形式。现在可以在函数名前使用 auto 关键字,在参数列表之后后置返回类型。例如:

auto  foo (int  x ) - >  int ;

后置返回类型为函数作用域。对于像 int 这样简单的类型,两种写法没有区别。但对于复杂的情况,例如类域中的类型声明或者以函数参数的形式书写的类型,写法的不同会造成区别。

优点

后置返回类型是显式地指定 Lambda表达式 的返回值的唯一方式。某些情况下,编译器可以自动推导出Lambda表达式的返回类型,但并不是在所有的情况下都能实现。即使编译器能够自动推导,显式地指定返回类型也能让读者更明了。

有时在已经出现了的函数参数列表之后指定返回类型,能够让书写更简单,也更易读,尤其是在返回类型依赖于模板参数时。
例如:

template  < class  T , class  U >  auto  add (T  t , U  u ) - >  
decltype (t  +  u );

对比下面的例子:

template  < class  T , class  U >  decltype (declval < T &> () +  declval < U &gt ;) add (T  t , U  u );

缺点

后置返回类型相对来说是非常新的语法,而且在C和Java中都没有相似的写法,因此可能对读者来说比较陌生。
在已有的代码中有大量的函数声明,你不可能把它们都用新的语法重写一遍。因此实际的做法只能是使用旧的语法或者新旧混用。在这种情况下,只使用一种版本是相对来说更规整的形式。

结论

在大部分情况下,应当继续使用以往的函数声明写法,即将返回类型置于函数名前。只有在必要的时候(如Lambda表达式)或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法。但是后一种情况一般来说是很少见的,大部分时候都出现在相当复杂的模板代码中,而多数情况下不鼓励写这样 复杂的模板代码。

命名约定

最重要的一致性规则是命名管理。命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义:类型,变量,函数,常量,宏,等等,甚至。我们大脑中的模式匹配引擎非常依赖这些命名规则。

命名规则具有一定随意性,但相比按个人喜好命名,一致性更重要,所以无论你认为它们是否重要,规则总归是规则。

通用命名规则

总述

函数命名,变量命名,文件命名要有描述性; 少用缩写。

说明

尽可能使用描述性的命名,别心疼空间,毕竟相比之下让代码易于新读者理解更重要。不要用只有项目开发者能理解的缩写,也不要通过砍掉几个字母来缩写单词。

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 ;               //删减了若干字母。

注意,一些特定的广为人知的缩写是允许的,例如用 i 表示迭代变量和用 T 表示模板参数。

模板参数的命名应当遵循对应的分类:类型模板参数应当遵循 类型命名 的规则,而非类型模板应当 遵循变量命名 的规则。

文件命名

总述

文件名要全部小写,可以包含下划线(_)或连-字符(),依照项目的约定。如果没有约定,那么“ _” 更好。

说明

可接受的文件命名示例:

my_useful_class.cpp
my-useful-class.cpp
myusefulclass.cpp
myusefulclass_test.cpp //  _unittest
状语从句:  _regtest 已弃用。

C ++文件要以 .cpp结尾,头文件以 .h 结尾。专门插入文本的文件则以 .inc 结尾,参见 头文件自足。

不要使用已经存在于 /usr/include 下的文件名(Yang.Y注:即编译器搜索系统头文件的路径),如 db.h。

通常应尽量让文件名更加明确。 http_server_logs.h 就比 logs.h 要好。定义类时文件名一般成对出现,如 foo_bar.h 和 foo_bar.cpp,对应于类 FooBar。

联内必须函数放在 .h 文件中。如果内联函数比较短,就直接放在 .h 中。

类型命名

总述

类型名称的每个单词首字母均大写,不包含下划线: MyExcitingClass, MyExcitingEnum。

说明

所有类型命名 - 类,结构体,类型定义(typedef),枚举,类型模板参数 - 均使用相同约定,即以大写字母开始,每个单词首字母均大写,不包含下划线。例如:

//类和结构体
类 UrlTable  {  ...
class  UrlTableTester  {  ...
struct  UrlTableProperties  {  ...

//类型定义
typedef  hash_map < UrlTableProperties  * , string >  PropertiesMap ;

//使用别名
使用 PropertiesMap  =  hash_map < UrlTableProperties  * , string > ;

//枚举
enum  UrlTableErrors  {  ...

变量命名

总述

变量(包括函数参数)和数据成员名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾,但结构体的就不用,如: a_local_variable, a_struct_data_member, a_class_data_member_。

说明

普通变量命名

举例

字符串 table_name ; //好 - 用下划线。
字符串 表名; //好 - 全小写。
字符串 tableName ; //差 - 混合大小写
类数据成员

不管是静态的还是非静态的,类数据成员都可以和普通变量一样,但要接下划线。


类 TableInfo  {
 ...
private :
 string  table_name_ ;   //好 - 后加下划线。
 字符串 tablename_ ;    //好。
 静态 池< TableInfo > *  pool_ ;   //好。
};
结构体变量

不管是静态的还是非静态的,结构体数据成员都可以和普通变量一样,不用像类那样接下划线:

struct  UrlTableProperties  {
 string  name ;
 int  num_entries ;
 静态 池< UrlTableProperties > *  池;
};

结构体与类的使用讨论,参考 结构体与类。

常量命名

总述

声明为 constexpr 或 const 的变量,或在程序运行期间其值始始保持不变的,命名时以“k”开头,大小写混合。例如:

const  int  kDaysInAWeek  =  7 ;

说明

所有具有静态存储类型的变量(例如静态变量或全局变量,参见 存储类型)都应当以此方式命名。对于其他存储类型的变量,如自动变量等,这条规则是可选的。如果不采用这条规则,就按照一般的变量命名规则。

命名空间命名

总述

命名空间以小写字母命名。最高级命名空间的名字取决于项目名称。要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突。

顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字。命名空间中的代码,应当存放于和命名空间的名字匹配的文件夹或其子文件夹中。

注意 不使用缩写作为名称 的规则同样适用于命名空间。命名空间中的代码极少需要涉及命名空间的名称,因此没有必要在命名空间中使用缩写。

要避免嵌套的命名空间与常见的顶级命名空间发生名称冲突。由于名称查找规则的存在,命名空间之间的冲突完全有可能导致编译失败。尤其是,不要创建嵌套的 std 命名空间。建议使用更独特的项目标识符(websearch::index, websearch::index_util)而非常见的极易发生冲突的名称(比如 websearch::util)。

对于 internal 命名空间,要当心加入到同一 internal 命名空间的代码之间发生冲突(由于内部维护人员通常来自同一团队,因此常有可能导致冲突)。在这种情况下,请使用文件名以使内部名称独一无二(例如对于 frobber.h,使用 websearch::index::frobber_internal)。

枚举命名

总述

枚举命名应当状语从句: 常量 或 宏 harmony和谐: kEnumName 或是 ENUM_NAME。

说明

单独枚举值应该优先采用 常量 的命名方式。但 宏 方式的命名也可以接受。
枚举名 UrlTableErrors (以及 AlternateUrlTableErrors)是类型,所以要用大小写混合的方式。


enum  UrlTableErrors  {
   kOK  =  0 ,
   kErrorOutOfMemory ,
   kErrorMalformedInput ,
};
枚举 AlternateUrlTableErrors  {
   OK  =  0 ,
   OUT_OF_MEMORY  =  1 ,
   MALFORMED_INPUT  =  2 ,
};

宏命名

总述

你并不打算 使用宏,对吧?如果你一定要用,像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN。

说明

参考 预处理宏 ; 通常 不应该 使用宏。如果不得不使用,其命名像枚举命名一样全部大写,使用下划线:

#define ROUND(x)...
#define PI_ROUNDED 3.0

命名规则的特例

总述

如果你命名的实体与已有C / C ++实体相似,可参考现有命名策略。


bigopen():函数名,参照  open() 的形式

uint: typedef

bigpos:  struct 或  class,参照  pos 的形式

sparse_hash_map:STL型实体; 参照STL命名约定

LONGLONG_MAX:常量,如同 INT_MAX

注意

注释虽然写起来很痛苦,但对保证代码可读性至关重要。下面的规则描述了如何注释以及在哪儿注释。当然也要记住:注释固然很重要,但最好的代码应当本身就是文档。有意义的类型名和变量名,要远胜过要用注释解释的含糊不清的名字。

你写的注释是给代码读者看的,也就是下一个需要理解你的代码的人。所以慷慨些吧,下一个读者可能就是你!

注释风格

总述

使用 // 或 /** * */ 统一就好。

说明

// 或 /** * */ 都可以; 但 //更 常用。要在如何注释及注释风格上确保统一。

文件注释

总述

在每一个文件开头加入版权公告。

文件注释描述了该文件的内容。如果一个文件只声明,或实现或测试了一个对象,并且这个对象已经在它的声明处进行了详细的注释,那么就没有必要再加上文件注释。除此之外的其他文件都需要文件注释。

说明

法律公告和作者信息

每个文件都应该包含许可证引用。为项目选择合适的许可证版本(比如,Apache 2.0,BSD,LGPL,GPL)

如果你对原始作者的文件做了重大修改,请考虑删除原作者信息。

文件内容

如果一个 .h 文件声明了多个概念,则文件注释应当对文件的内容做一个大致的说明,同时说明各个概念之间的联系。一个一到两行的文件注释就足够了,对于每个概念的详细文档应当放在各个概念中,而不是文件注释中。

不要在 .h 和 .cpp 之间复制注释,这样的注释偏离了注释的实际意义。

类注释

总述

每个类的定义都要附带一份注释,描述类的功能和用法,除非它的功能相当明显。


//遍历GargantuanTable的内容。
//示例:
// GargantuanTableIterator * iter = table-> NewIterator();
// it for(iter-> Seek(“foo”);!iter-> done(); iter-> Next()){
// process(iter-> key(),iter-> value());
//}
//删除它;
类 GargantuanTableIterator  {
 ...
};

说明

类注释应当为读者理解如何使用与何时使用类提供足够的信息,同时应当提醒读者在正确使用此类时应当考虑的因素。如果类有任何同步前提,请用文档说明。如果该类的实例可被多线程访问,要特别注意文档说明多线程环境下相关的规则和常量使用。

如果你想用一小段代码演示这个类的基本用法或通常用法,放在类注释里也非常合适。

如果类的声明和定义分开了(例如分别放在了 .h 和 .cpp 文件中),此时,描述类用法的注释应当和接口定义放在一起,描述类的操作和实现的注释应当和实现放在一起。

函数注释

总述

函数声明处的注释描述函数功能; 定义处的注释描述函数实现。

说明

函数声明:基本上每个函数声明处前都应当加上注释,描述函数的功能和用途。只有在函数的功能简单而明显时才能省略这些注释(例如,简单的取值和设值函数)。注释使用叙述式(“打开文件”)而非指令式(“打开文件”); 注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作。那是函数定义部分的事情。

函数声明处注释的内容:

函数的输入输出。
对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数。
函数是否分配了必须由调用者释放的空间。
参数是否可以为空指针。
是否存在函数使用上的性能隐患。
如果函数是可重入的,其同步提提是什么?
举例如下:

//返回此表的迭代器。
当迭代器完成时,它是
客户端的责任//并且一旦
创建
迭代器的GargantuanTable对象被删除,它就不能使用迭代器。//
//迭代器最初位于表的开始位置。
//
//此方法等同于:
// Iterator * iter = table-> NewIterator();
// iter-> Seek(“”);
//返回iter;
//如果您要立即寻找到
返回的迭代器
中的其他位置,则使用NewIterator()会更快,并避免额外的查找。
Iterator * GetIterator () const;
但也要避免罗罗嗦嗦,或者对显着易见的内容进行说明。下面的注释就没有必要加上“否则返回false”,因为已经暗含其中了:
//如果表不能包含更多条目,则返回true。
bool IsTableFull ();
注释函数重载时,注释的重点应该是函数中被重载的部分,而不是简单的重复被重载的函数的注释。多数情况下,函数重载不需要额外的文档,因此也没有必要加上注释。

注释构造/析构函数,切记读代码的人知道构造/析构函数的所有功能,所以“销毁这一对象”这样的注释是没有意义的。你应该注意的是注意构造函数对参数做了什么(例如,是否取得指针所有权)以及析构函数清理了什么。如果都是些无关紧要的内容,直接省掉注释。析构函数前没有注释是很正常的。

函数定义

如果函数的实现过程中用到了很巧妙的方式,那么在函数定义处应当加上解释性的注释。例如,你所使用的编程技巧,实现的大致步骤,或解释如此实现的理由。举个例子,你可以说明为什么函数的前半部分要加锁而后半部分不需要。

不要 从 .h 文件或其他地方的函数声明处直接复制注释。简要重述函数功能是可以的,但注释重点要放在如何实现上。

变量注释

总述

通常变量名本身足以很好说明变量用途。某些情况下,也需要额外的注释说明。

说明

类数据成员:每个类数据成员(也叫实例变量或成员变量)都应该用注释说明用途。如果有非变量的参数(例如特殊值,数据成员之间的关系,生命周期等)不能够使用类型与变量名明确表达,则应当加上注释。然而,如果变量类型与变量名已经足够描述一个变量,那么就不需要加上注释。

特别地,如果变量可以接受 NULL 或 -1 等警戒值,须加以说明。比如:


private :
//用于限制检查表访问。-1意味着
//我们还不知道表中有多少个条目。
int  num_total_entries_ ;
全局变量

和数据成员一样,所有全局变量也要注释说明含义及用途,以及作为全局变量的原因。比如:

//在此回归测试中我们经历的测试用例的总数。
const  int  kNumTestCases  =  6 ;

实现注释

总述

对于代码中巧妙的,晦涩的,有趣的,重要的地方加以注释。

说明

代码前注释:巧妙或复杂的代码段前要加注释。比如:

//将结果除以2,考虑到x
//包含来自add的进位。
for  (int  i  =  0 ;  i  <  result - > size ();  i ++ ) {
 x  =  (x  <<  8 ) +  (* result )[ i ];
 (* 结果)[ i ]  =  x  >>  1 ;
 x  &=  1 ;
}
行注释

比较隐晦的地方要在行尾加入注释。在行尾空两格进行注释。比如:

//如果我们有足够的内存,也可以对数据部分进行mmap。
mmap_budget  =  max < int64 > (0 , mmap_budget  -  index_ - > length ());
if  (mmap_budget  > =  data_size_  &&  !MmapData (mmap_chunk_bytes , mlock ))
 return ;   //错误已经记录。
注意,这里用了两段注释分别描述这段代码的作用,并提示函数返回错误已经被记入日志。

如果你需要连续进行多行注释,可以使之对齐获得更好的可读性:

DoSomething ();                   //在这里发表评论,以便评论排成一行。
DoSomethingElseThatIsLonger ();   //代码和注释之间有两个空格。
{  //允许打开一个新的作用域时,在注释之前的一个空格
 // //因此注释与下面的注释和代码一起排列。
 DoSomethingElse ();   //通常在行注释之前有两个空格。
}
std :: vector < string >  list {
                   //支撑列表中的注释描述下一个元素...
                   “First item” ,
                   // ..并且应该适当地对齐。
};

函数参数注释

如果函数参数的意义不明显,考虑用下面的方式进行弥补:

如果参数是一个字面常量,并且这一常量在多处函数调用中被使用,用以推断它们一致,你应该用一个常量名让这个约定变得更明显,并且保证这一约定不会被打破。
考虑更改函数的签名,让某个 bool 类型的参数变为 enum 类型,这样可以让这个参数的值表达其意义。
如果某个函数有多个配置选项,你可以考虑定义一个类或结构体以保存所有的选项,并传入类或结构体的实例。这样的方法有许多优点,例如这样的选项可以在调用处用变量名引用,这样就能清晰地表明其意义。同时也减少了函数参数的数量,使得函数调用更易读也易写。除此之外,以这样的方式,如果你使用其他的选项,就无需对调用点进行更改。
用具名变量代替大段而复杂的嵌套表达式。
万不得已时,才考虑在调用点用注释阐明参数的意义。

不允许的行为

不要描述显而易见的现象, 永远不要 用自然语言翻译代码作为注释,除非即使对深入理解C ++的读者来说代码的行为都是不明显的。要假设读代码的人C ++水平比你高,即便他/她可能不知道你的用意.

你所提供的注释应当解释代码 为什么 要这么做和代码的目的,或者最好是让代码自文档化.

标点,拼写和语法

总述

注意标点,拼写和语法; 写的好的注释比差的要易读的多。

说明

注释的通常写法是包含正确大小写和结尾句号的完整叙述性语句。大多数情况下,完整的句子比句子片段可读性更高。短一点的注释,比如代码行尾注释,可以随意点,但依然要注意风格的一致性。

虽然被别人指出该用分号时却用了逗号多少有些尴尬,但清晰易读的代码还是很重要的。正确的标点,拼写和语法对此会有很大帮助。

行长度

总述

每一行代码字符数不超过80。

我们也认识到这条规则是有争议的,但很多已有代码都遵照这一规则,因此我们感觉一致性更重要。

优点

提倡该原则的人认为强迫他们调整编辑器窗口大小是很野蛮的行为。很多人同时并排开几个代码窗口,根本没有多余的空间拉伸窗口。大家都把窗口最大尺寸加以限定,并且80列宽是传统标准。那么为什么要改变呢?

缺点

反对该原则的人则认为更宽的代码行更易阅读。80列的限制是上个世纪60年代的大型机的古板缺陷;现代设备具有更宽的显示屏,可以很轻松地显示更多代码。

结论

80个字符是最大值。

如果无法在不伤害易读性的条件下进行断行,那么注释行可以超过80个字符,这样可以方便复制粘贴。例如,带有命令示例或URL的行可以超过80个字符。长所有游戏的路径 #include 语句可以超出80列。

文件头保护 可以无视该原则。

函数调用

总述

要么一行写完函数调用,要么在圆括号里对参数分行,要么参数另起一行且缩进四格。如果没有其它顾虑的话,尽可能精简行数,比如把多个参数适当地放在同一行里。

说明

函数调用遵循如下形式:

bool  retval  =  DoSomething (argument1 , argument2 , argument3 );
如果同一行放不下,可断为多行,后面每一行都和第一个实参对齐,左圆括号后和右圆括号前不要留空格:

bool  retval  =  DoSomething (averyveryveryverylongargument1 ,
                         argument2 , argument3 );
参数也可以放在次行,缩进四格:

如果 (...) {
 ...
 ...
 if  (...) {
   DoSomething (
       argument1 , argument2 ,  // 4空格缩进
       argument3 , argument4 );
 }

把多个参数放在同一行以减少函数调用所需的行数,除非影响到可读性。有人认为把每个参数都独立成行,不仅更好读,而且方便编辑参数。

循环和开关选择语句

总述

switch 语句可以使用大括号分段,以表明cases之间不是连在一起的。在单语句循环里,括号可用可不用。循环空应行业释义体育使用 {} 或 continue。

说明

switch 语句中的 case 块可以使用大括号也可以不用,取决于你的个人喜好。如果用的话,要按照下文所述的方法。

如果有不满足 case 条件的枚举值, switch 应该default 总是包含一个 匹配(如果有输入值没有case去处理,编译器将给出警告)。如果 default 应该永远执行不到,简单的加条 assert:


switch  (var ) {
 case  0 : {   // 2空格缩进
   ...       // 4空格缩进
   break ;
 }
 案例 1 : {
   ...
   break ;
 }
 default : {
   assert (false );
 }
}

在单语句循环里,括号可用可不用:


for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i )
 printf (“我爱你\ n ” );

for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i ) {
 printf (“我拿回来\ n ” );
}
空循环体应使用  {} 或  continue,而不是一个简单的分号。

while  (condition ) {
 //反复循环直到条件失效。
}
for  (int  i  =  0 ;  i  <  kSomeNumber ;  ++ i ) {}   //可 - 空循环体。
同时 (条件) 继续;   //可 -  contunue表明没有逻辑。
while  (condition );   //差 - 看起来仅仅只是while / loop的部分之一。

构造函数初始值列表

*总述**

构造函数初始化列表放在同一行或按四格缩进并排多行。

说明

下面两种初始值列表方式都可以接受:



//如果所有变量能放在同一行:
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空格缩进
     some_other_var_ (var  +  1 ) {   //列队
 DoSomething ();
}

//右大括号}可以和左大括号{放在同一行
//如果这样做合适的话
MyClass :: MyClass (int  var )
   : some_var_ (var ) {}

结语

代码规范是项目开发非常重要的一点,作为一名合格的程序猿应该将代码规范熟记于心。


部分资料来源于网络,版权属其原著者所有,只供学习交流之用。如有侵犯您的权益,请联系【公众号:码农印象】删除,可在下方评论,亦可邮件至ysluckly.520@qq.com。互动交流时请遵守宽容、换位思考的原则。

×

喜欢就点赞,疼爱就打赏

(function(){ var bp = document.createElement('script'); bp.src = '//push.zhanzhang.baidu.com/push.js'; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();
休闲小游戏