1、G ogleG G G C+编 程 风 格 指 南 ( 一 )背景背景背景背景Gogle的开源项目大多使用 C+开发。每一个 C+程序员也都知道, C+具有很多强大的语言特性,但这种强大不可避免的导致它的复杂,这种复杂会使得代码更易于出现 bug、难于阅读和维护。 本指南的目的是通过详细阐述在 C+编码时要怎样写、不要怎样写来规避其复杂性。这些规 则可在允许代码有效使用 C+语言特性的同时使其易于管理。风格,也被视为可读性,主要 指称管理 C+代码的习惯。使用 术语风格有点用词不当,因 为 这些习惯远不止源代码文件格式这么简单。 使代码易于管理的方法之一是增强代码一致性, 让别人可以读懂你的代
2、码是很重要的, 保持统 一编程风格意味着可以轻松根据 “模式匹配 ”规则推断各种符号的含义。 创 建通用的、 必 需的习惯 用语和模式可以使代码更加容易理解, 在某些情况下改变一些编程风格可能会是好的选择, 但我 们还是应该遵循一致性原则,尽量不这样去做。 本指南的另一个观点是 C+特性的臃肿。 C+是一门包含大量高级特性的巨型语言, 某 些情 况下, 我们会限制甚至禁止使用某些特性使代码简化, 避免可能导致的各种问题, 指南中列举了 这类特性,并解释说为什么这些特性是被限制使用的。由 Gogle开发的开源项目将遵照本指南约定。注意:本指南并非 C+教程,我们假定读者已经对 C+非常熟悉。头文
3、件头文件头文件头文件通常,每一个 .c文件( C+的源文件)都有一个对应的 .h文件(头文件),也有一些例外,如单元测试代码和只包含 main()的 .c文件。正确使用头文件可令代码在可读性、文件大小和性能上大为改观。 下面的规则将引导你规避使用头文件时的各种麻烦。 1.#define的保护的保护的保护的保护所有头文件都应该使用 #define防止头文件被 多 重 包 含 ( multipleinclusion) ,命 名格式当 是 :_H_为保证唯一性, 头 文件的命名应基于其所在项目源代码树的全路径。 例如, 项 目foo中的头文件 foo/src/bar/baz.h按如下方式保护:#if
4、ndefFO_BAR_BAZ_H#defineFO_BAR_BAZ_H.#endif/FO_BAR_BAZ_H2.头文件依赖头文件依赖头文件依赖头文件依赖使用 前置声明( forwardeclartions) 尽量减少 .h文件中 #include的数量。当一个头文件被包含的同时也引入了一项新的 依赖( depndency) ,只要该头文件被修改,代码就要重新编译。 如果你的头文件包含了其他头文件, 这些头文件的任何改变也将导致那些 包含了你的头文件的代码重新编译。 因此, 我们宁可尽量少包含头文件, 尤其是那些包含在其他 头文件中的。使用前置声明可以显著减少需要包含的头文件数量。 举例说明:
5、 头文件中用到类 File, 但 不需 要访问 File的声明,则头文件中只需前置声明 classFile;无需 #include“file/base/file.h“。在头文件如何做到使用类 Fo而无需访问类的定义?1)将数据成员类型声明为 Fo*或 Fo /经常使用的符号bolAtEof()returnpos_=EOF;/使用本命名空间内的符号 EOF/namespace然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函数, 而不是不具名命名空间的成员。像上文展示的那样,不具名命名空间结束时用注释 /namespace标识。不能在 .h文件中使用不具名命名空间。
6、2)具 名 命 名 空 间 ( NamedNamespaces)具名命名空间使用方式如下:命名空间将 除文件包含、全局标识的声明 /定义以及类的前置声明外 的整个源文件封装起来,以同其他命名空间相区分。 /.h文件namespacemynamespace/所有声明都置于命名空间中/注意不要使用缩进clasMyClaspublic:.voidFo();/namespacemynamespace/.c文件namespacemynamespace/函数定义都置于命名空间中voidMyClas:Fo()./namespacemynamespace通常的 .c文件会包含更多、更复杂的细节,包括对其他命名
7、空间中类的引用等。#include“a.h“DEFINE_bol(someflag,false,“dumyflag“);clasC;/全局命名空间中类 C的前置声明namespaceaclasA;/命名空间 a中的类 a:A的前置声明naespaceb.codeforb. /b中的代码/namespaceb不要声明命名空间 std下的任何内容,包括标准库类的前置声明。声明 std下的实体会导致不明确的行为,如,不可移植。声明标准库下的实体,需要包含对应的头文件。最好不要使用 using指示符,以保证命名空间下的所有名称都可以正常使用。/禁止 污染命名空间usingamespacefo;在 .c
8、文件、 .h文件的函数、方法或类中,可以使用 using。/允许: .c文件中/.h文件中,必须在函数、方法或类的内部使用using:fo:bar;在 .c文件、 .h文件的函数、方法或类中,还可以使用命名空间别名。/允许: .c文件中/.h文件中,必须在函数、方法或类的内部使用namespacefbz=:fo:bar:baz;2.嵌套类(嵌套类(嵌套类(嵌套类(NestdClas)当公开嵌套类作为接口的一部分时, 虽然可以直接将他们保持在全局作用域中, 但将嵌套类的 声明置于命名空间中是更好的选择。定 义 : 可以在一个类中定义另一个类,嵌套类也称 成员类( meberclas) 。clas
9、Foprivate:/Bar是嵌套在 Fo中的成员类clasBar.;优 点 : 当嵌套(成员)类只在 被嵌套类( enclosingclas) 中使用时很有用,将其置于被嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在 .c文件中定义嵌套类, 避免在被嵌套类中包含嵌套类的定义, 因 为嵌套类的定义通常只与 实现相关。 缺 点 : 只能在被嵌套类的定义中才能前置声明嵌套类。因此,任何使用 Fo:Bar*指针的头文件必须包含整个 Fo的声明。结 论 : 不要将嵌套类定义为 public,除非它们是接口的一部分,比如,某个方法使用了这个类的一系列选项。 3.
10、非成员函数(非成员函数(非成员函数(非成员函数(Nonmeber)、静态成员函数()、静态成员函数()、静态成员函数()、静态成员函数(StaicMember)和全局函数()和全局函数()和全局函数()和全局函数(GlobalFunctions)使用命名空间中的非成员函数或静态成员函数,尽量不要使用全局函数。 优 点 : 某些情况下, 非成员函数和静态成员函数是非常有用的, 将非成员函数置于命名空间中 可避免对全局作用域的污染。 缺 点 : 将非成员函数和静态成员函数作为新类的成员或许更有意义, 当它们需要访问外部资源 或具有重要依赖时更是如此。 结 论 :有时, 不 把函数限定在类的实体中是
11、有益的, 甚 至需要这么做, 要 么作为静态成员, 要 么作为 非成员函数。 非成员函数不应依赖于外部变量, 并尽量置于某个命名空间中。 相比单纯为了封装 若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间。 定义于同一编译单元的函数, 被其他编译单元直接调用可能会引入不必要的耦合和连接依赖; 静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置于独立库的命名空间中。如果你确实需要定义非成员函数,又只是在 .c文件中使用它,可使用不具名命名空间或 staic关联(如 staticintFoo().)限定其作用域。4.局部变量(局部变量(局部变量(局部变量(LocalVari
12、bles)将函数变量尽可能置于最小作用域内,在声明变量时将其初始化。 C+允许在函数的任何位置声明变量。我们提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。 这使得代码易于阅读, 易于定位变量的声明位置、 变量类型和初始值。 特别是, 应使用初始化代替声明 +赋值的方式。inti;i=f(); /坏 初始化和声明分离ntj=g();/好 初始化时声明注 意 : gc可正确执行 for(inti=0;i实 现 继 承 接 口 继 承 私 有 继 承 , 子 类 重 载 的 虚 函 数也 要 声 明 virtual关 键 字 , 虽 然编 译 器 允 许 不 这 样 做 ; 7.避 免 使
13、 用 多 重 继 承 , 使 用 时 , 除 一 个 基类 含 有 实 现 外 , 其 他基 类 均 为 纯 接 口 ;8.接 口 类 类 名 以 Interface为 后 缀 , 除 提 供 带 实 现 的 虚 析构 函 数 、 静 态 成 员 函数 外 , 其 他 均为 纯 虚 函 数 , 不 定 义 非 静 态 数 据成 员 , 不 提 供 构 造 函 数, 提 供 的 话 , 声 明 为 protectd;9.为 降 低 复 杂 性 , 尽 量 不 重 载 操 作 符 , 模板 、 标 准 类 中 使 用 时提 供 文 档 说 明 ;10.存 取 函 数 一 般 内 联 在 头 文 件
14、 中 ;1.声 明 次 序 : public-protectd-private;12.函 数 体 尽 量 短 小 、 紧 凑 , 功 能 单 一 。G ogleG G G C+编 程 风 格 指 南 ( 四 )Gogle特有的风情特有的风情特有的风情特有的风情Gogle有很多自己实现的使 C+代码更加健壮的技巧、 功能, 以及有异于别处的 C+的使 用方式。 1.智能指针(智能指针(智能指针(智能指针(SmartPointers)如果确实需要使用智能指针的话, scoped_ptr完全可以胜任。在非常特殊的情况下,例如对 STL容器中对象,你应该只使用 std:tr1:shared_ptr,任
15、何情况下都不要使用 auto_ptr。“智能 ”指针看上去是指针, 其实是附加了语义的对象。 以 scoped_ptr为例, scoped_ptr被 销毁时,删除了它所指向的对象。 shared_ptr也是如此,而且, shared_ptr实现了 引用计数( refrence-countig) ,从而只有当它所指向的最后一个对象被销毁时,指针才会被删除。一般来说, 我们倾向于设计对象隶属明确的代码, 最明确的对象隶属是根本不使用指针, 直接 将对象作为一个 域( field) 或局部变量使用。另一种极端是引用计数指针不属于任何对象,这样设计的问题是容易导致循环引用或其他导致对象无法删除的诡异条
16、件,而 且在每一次拷贝或赋 值时连原子操作都会很慢。 虽然不推荐这么做,但有些时候,引用计数指针是最简单有效的解决方案。译 者 注 : 看 来 , Gogle所 谓 的 不 同 之 处 , 在 于 尽 量 避 免 使 用智 能 指 针 :D, 使 用 时 也 尽 量 局 部化 , 并 且 , 安 全 第 一 。其他其他其他其他C+特性特性特性特性1.引用参数(引用参数(引用参数(引用参数(RefrenceArguments)所以按引用传递的参数必须加上 const。定 义 : 在 C语言中,如 果函数需要修改变量的值, 形 参 ( parmetr) 必须为指针,如 intfo(int*pval
17、)。在 +中,函数还可以声明引用形参: intfo(int事实上这是一个硬性约定: 输入参数为值或常数引用, 输出参数为指针; 输入参数可以是常数 指针,但不能使用非常数引用形参。在强调参数不是拷贝而来, 在对象生命期内必须一直存在时可以使用常数指针, 最好将这些在 注释中详细说明。 bind2nd和 me_fun等 STL适配器不接受引用形参, 这 种情况下也必须以 指针形参声明函数。 2.函数重载(函数重载(函数重载(函数重载(FunctionOverloading)仅在输入参数类型不同、 功 能相同时使用重载函数 ( 含构造函数) , 不要使用函数重载模仿缺 省函数参数。 定 义 : 可
18、以定义一个函数参数类型为 consttringvoidAnalyze(constchar*text,size_ttextlen);优 点 : 通过重载不同参数的同名函数, 令 代码更加直观, 模 板化代码需要重载, 同时为访问者 带来便利。缺 点 : 限制使用重载的一个原因是在特定调用处很难确定到底调用的是哪个函数, 另一个原因 是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。此外在阅读库的客户端代码 时,因缺省函数参数造成不必要的费解。 结 论 : 如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用 ApendString()、ApendIt()而不是 Apend()。3
19、.缺省参数(缺省参数(缺省参数(缺省参数(DefaultArguments)禁止使用缺省函数参数。 优 点 : 经常用到一个函数带有大量缺省值, 偶尔会重写一下这些值, 缺省参数为很少涉及的例 外情况提供了少定义一些函数的方便。 缺 点 : 大家经常会通过查看现有代码确定如何使用 API, 缺省参数使得复制粘贴以前的代码难 以呈现所有参数,当缺省参数不适用于新代码时可能导致重大问题。 结 论 : 所有参数必须明确指定,强制程序员考虑 API和传入的各参数值,避免使用可能不为程序员所知的缺省参数。 4.变长数组和变长数组和变长数组和变长数组和aloca(Varible-LngthAraysand
20、aloca())禁止使用变长数组和 aloca()。优 点 : 变长数组具有浑然天成的语法,变长数组和 aloca()也都很高效。缺 点 : 变长数组和 aloca()不是标准 C+的组成部分,更重要的是,它们在 堆栈( stack) 上根据数据分配大小可能导致难以发现的内存泄漏: “在我的机器上运行的好好的,到了产品中却莫名其妙的挂掉了 ”。结 论 : 使用安全的 分配器( alocator) ,如 scoped_ptr/scoped_ary。5.友元(友元(友元(友元(Friends)允许合理使用友元类及友元函数。 通常将友元定义在同一文件下, 避免读者跑到其他文件中查找其对某个类私有成员
21、的使用。 经 常用到友元的一个地方是将 FoBuilder声明为 Fo的友元, FoBuilder以便可以正确构造 Fo的内部状态, 而无需将该状态暴露出来。 某些情况下, 将一个单元测试用类声明为待测类的友 元会很方便。友元延伸了 ( 但没有打破) 类 的封装界线, 当 你希望只允许另一个类访问某个成员时, 使 用友 元通常比将其声明为 public要好得多。当然,大多数类应该只提供公共成员与其交互。6.异常(异常(异常(异常(Exceptions)不要使用 C+异常。优 点 : 1)异常允许上层应用决定如何处理在底层嵌套函数中发生的 “不可能发生 ”的失败,不像出错代码的记录那么模糊费解;
22、2)应用于其他很多现代语言中, 引入异常使得 C+与 Python、 Jav及其他与 C+相近的 语言更加兼容; 3)许多 C+第三方库使用异常,关闭异常将导致难以与之结合;4)异常是解决构造函数失败的唯一方案, 虽然可以通过 工厂函数 ( factoryfunction) 或 Init()方法模拟异常,但他们分别需要堆分配或新的 “非法 ”状态;5)在 测试框架( testingframework) 中,异常确实很好用。缺 点 :1)在现有函数中添加 throw语句时, 必须检查所有调用处, 即使它们至少具有基本的异常安 全保护,或者程序正常结束,永远不可能捕获该异常。例如: iff()ca
23、lsg()calsh(), h抛出被 f捕获的异常, g就要当心了,避免没有完全清理;2)通俗一点说,异常会导致程序 控制流( controlflow) 通过查看代码无法确定:函数有可能在不确定的地方返回, 从而导致代码管理和调试困难, 当然, 你可以通过规定何时何地如何使 用异常来最小化的降低开销,却给开发人员带来掌握这些规定的负担; 3)异常安全需要 RAI和不同编码实践。轻松、正确编写异常安全代码需要大量支撑。允许使用异常; 4)加入异常使二进制执行代码体积变大,增加 了编译时长(或 许影响不大),还可能增加地 址空间压力; 5)异常的实用性可能会刺激开发人员在不恰当的时候抛出异常, 或
24、 者在不安全的地方从异常 中恢复, 例如, 非法用户输入可能导致抛出异常。 如果允许使用异常会使得这样一篇编程风格指 南长出很多( 译者注,这个理由有点牵强 :-()!结 论 : 从表面上看, 使用异常利大于弊, 尤其是在新项目中, 然而, 对于现有代码, 引入异常会牵连 到所有依赖代码。 如果允许异常在新项目中使用, 在跟以前没有使用异常的代码整合时也是一个 麻烦。 因为 Gogle现有的大多数 C+代码都没有异常处理,引入 带有异常处理的新代码相当 困难。鉴于 Gogle现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大 一点, 迁 移过程会比较慢, 也 容易出错。 我
25、 们也不相信异常的有效替代方案, 如 错误代码、 断言 等 ,都是严重负担。 我们并不是基于哲学或道德层面反对使用异常,而是在实践的基础上。因为我们希望使用 Gogle上的开源项目, 但项目中使用异常会为此带来不便, 因为我们也建议不要在 Gogle上的开源项目中使用异常,如果我们需要把这些项目推倒重来显然不太现实。 对于 Windows代码来说,这一点有个例外(等到最后一篇吧 :D)。译 者 注 : 对 于 异 常 处 理 , 显 然 不 是 短 短 几 句 话 能 够 说 清楚 的 , 以 构 造 函 数 为例 , 很 多 C+书 籍上 都 提 到 当 构 造 失 败 时 只 有 异 常可
26、 以 处 理 , Gogle禁 止 使 用 异 常 这 一 点 , 仅 仅 是 为 了 自 身 的方 便 , 说 大 了 , 无 非 是 基 于 软 件管 理 成 本 上 , 实 际 使 用中 还 是 自 己 决 定 。 7.运行时类型识别(运行时类型识别(运行时类型识别(运行时类型识别(Run-TimeTypeInformation,RTI)我们禁止使用 RTI。定 义 : RTI允许程序员在运行时识别 C+类对象的类型。优 点 : RTI在某些单元测试中非常有用, 如在进行工厂类测试时用于检验一个新建对象是否为期望 的动态类型。除测试外,极少用到。 缺 点 : 运行时识别类型意味著设计本身
27、有问题, 如果你需要在运行期间确定一个对象的类型, 这通常说明你需要重新考虑你的类的设计。 结 论 :除单元测试外,不要使用 RTI, 如果你发现需要所写代码因对象类型不同而动作各异的话, 考虑换一种方式识别对象类型。 虚函数可以实现随子类类型不同而执行不同代码,工作都是交给对象本身去完成。 如果工作在对象之外的代码中完成,考 虑双重分发方案,如 Visitor模式, 可 以方便的在对象 本身之外确定类的类型。 如果你认为上面的方法你掌握不了,可以使用 RTI, 但务必请三思,不要去手工实现一个 貌 似RTI的方案( RTI-likeworkaround) ,我们反对使用 RTI,同样反对贴上
28、类型标签的貌似类继承的替代方案( 译者注,使用就使用吧,不使用也不要造轮子 :D)。8.类型转换(类型转换(类型转换(类型转换(Casting)使用 static_casthostname.firstbar()-hostname.secondbar()-hostname.first,fo-bar()-hostname.second,strero(erno);你可能会说, “把流封装一下就会比较好了 ”, 这儿可以, 其他地方呢?而且不要忘了, 我们的 目标是使语言尽可能小,而不是添加一些别人需要学习的新的内容。 每一种方式都是各有利弊, “没有最好,只有更好 ”,简单化的教条告诫我们必须从中选
29、择其一,最后的多数决定是 printf+read/write。10.前置自增和自减(前置自增和自减(前置自增和自减(前置自增和自减(PreincremntandPredcremnt)对于迭代器和其他模板对象使用前缀形式( +i)的自增、自减运算符。定 义 :对于变量在自增( +i或 i+)或自减( -i或 i-)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增自减。 优 点 : 不考虑返回值的话,前置 自增( +i) 通常要比后置自增( i+)效率更高,因为后置 的自增自减需要对表达式的值 i进行一次拷贝,如果 i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自
30、增方式动作一样( 译者注,不考虑表达式的值,相 信你知道我在说什 么 ) ,为什么不直接使用前置自增呢? 缺 点 : C语言中,当表达式的值没有使用时,传统的做法是使用后置自增,特别是在 for循环中,有些人觉得后置自增更加易懂,因为这很像自然语言,主语( i)在谓语动词( +)前。结 论 : 对 简单数值 (非对象) 来说, 两 种都无所谓, 对迭代器和模板类型来说, 要使用前置自 增(自减)。1.const的使用(的使用(的使用(的使用(Useofconst)我们强烈建议你在任何可以使用的情况下都要使用 const。定 义 :在声明的变量或参数前加上关键字 const用于指明变量值不可修改
31、(如 constintfoo) , 为类中的函数加上 const限定表明该函数不会修改类成员变量的状态 (如 classFoointBar(charc)const;)。优 点 :人们更容易理解变量是如何使用的,编辑器可以更好地进行类型检测、更好地生成代码。人们对编写正确的代码更加自信, 因为他们知道所调用的函数被限定了能或不能修改变量值。 即使是在无锁的多线程编程中,人们也知道什么样的函数是安全的。缺 点 :如果你向一个函数传入 const变量,函数原型中也必须是 const的(否则变量需要const_cast类型转换),在调用库函数时这尤其是个麻烦。结 论 : const变量、数据成员、函数
32、和参数为编译时类型检测增加了一层保障,更好的尽早发现错误。因此,我们强烈建议在任何可以使用的情况下使用 const:1)如果函数不会修改传入的引用或指针类型的参数,这样的参数应该为 const;2)尽可能将函数声明为 const,访问函数应该总是 const,其他函数如果不会修改任何数据成员也应该是 const, 不要调用非 const函数, 不要返回对数据成员的非 const指针或引用 ;3)如果数据成员在对象构造之后不再改变,可将其定义为 const。然而, 也 不 要 对 const过 度 使 用 , 像 constint*const*constx;就有些过了,即 便这样写精确描述了 x
33、,其实写成 constint*x就可以了。关键字 mutable可以使用,但是在多线程中是不安全的,使用时首先要考虑线程安全。const位 置 :有人喜欢 intconst*foo形式不喜欢 constint*foo, 他 们认为前者更加一致因此可 读性更好:遵循了 总位于其描述的对象( int)之后的原则。但是,一致性原则不适用于此, “不要过度使用 ”的权威抵消了一致性使用。将 const放在前面才更易读,因为在自然语言中形容词( const)是在名词( int)之前的。这是说,我们提倡 const在前,并不是要求,但要兼顾代码的一致性!12.整型(整型(整型(整型(IntegrTypes
34、)C+内建整型中, 唯一用到的是 int,如 果程序中需要不同大小的变量, 可以使用 中的 精确宽度( precise-width) 的整型,如 int16_t。定 义 : C+没有指定整型的大小, 通 常人们认为 short是 16位, int是 32位, long是 32位, longlong是 64位。优 点 :保持声明统一。缺 点 : C+中整型大小因编译器和体系结构的不同而不同。结 论 :定义了 int16_t、 uint32_t、 int64_t等整型, 在需要确定大小的整型时可 以使用它们代替 short、 unsignedlonglong等,在 C整型中,只使用 int。 适
35、当 情 况 下 ,推荐使用标准类型如 size_t和 ptrdiff_t。最常使用的是,对整数来说,通常不会用到太大,如循环计数等,可以使用普通的 int。你可以认为 int至少为 32位,但不要认为它会多于 32位 ,需 要 64位整型的话,可以使用 int64_t或 uint64_t。对于大整数,使用 int64_t。不要使用 uint32_t等无符号整型, 除非你是在表示一个 位 组 ( bitpatern) 而不是一个数 值 。即使数值不会为负值也不要使用无符号类型, 使用 断言 ( asertion, 译 者注, 这一点很有道 理 ,计算机只会根据变量、返回值等有无符号确定数值正负,
36、仍然无法确定对错) 来保护数据。无 符 号 整 型 :有些人, 包 括一些教科书作者, 推 荐使用无符号类型表示非负数, 类 型表明了数值取值形式。 但是,在 C语言中,这一优点被由其导致的 bugs所淹没。看看:for(unsignedinti=fo.Length()-1;i=0;-i).上述代码永远不会终止! 有时 gc会发现该 bug并报警, 但 通常不会。 类似的 bug还会出现 在比较有符合变量和无符号变量时,主要是 C的 类型提升机制( type-romtionschem, C语言中各种内建类型之间的提升转换关系) 会致使无符号类型的行为出乎你的意料。因此,使用断言声明变量为非负数
37、,不要使用无符号型。 13.64位下的可移植性(位下的可移植性(位下的可移植性(位下的可移植性(64-bitPortabilty)代码在 64位和 32位的系统中,原则上应该都比较友好,尤其对于输出、比较、 结构对齐( structrealignment) 来说:1)printf()指定的一些类型在 32位和 64位系统上可移植性不是很好, C9标准定义了一 些可移植的格式。不幸的是, MSVC7.1并非全部支持,而且标准中也有所遗漏。所以有时我们就不得不自己定义丑陋的版本(使用标准风格要包含文件 inttypes.h):/printfmacrosforsize_t,inthestyleofi
38、ntypes.h#ifdef_LP64#define_PRIS_PREFIX“z#else#define_PRIS_PREFIX#endif/Usethesmacrosaftera%inaprintformatstring/togetcorect32/64bitbehavior,likethis:/size_tsize=records.ize();/printf(“%PRIuS“n“,size);#definePRIdS_PRIS_PREFIX“d#definePRIxS_PRIS_PREFIX“x#definePRIuS_PRIS_PREFIX“u#definePRIXS_PRIS_PREF
39、IX“X#definePRIoS_PRIS_PREFIX“o注意宏 PRI*会被编译器扩展为独立字符串, 因 此如果使用非常量的格式化字符串, 需 要将宏 的值而不是宏名插入格式中,在 使用宏 PRI*时同样可以在 %后 指 定 长 度 等 信 息 。例 如 , printf(“x=%30“PRIuS“n“,x)在 32位 Linux上将被扩展为 printf(“x=%30“u“n“,x),编译器会处理为 printf(“x=%30un“,x)。2)记住 sizeof(void*)!=sizeof(int),如果需要一个指针大小的整数要使用intptr_t。3)需要对结构对齐加以留心,尤其是对
40、于存储在磁盘上的结构体。在 64位系统中,任何拥有int64_t/uint64_t成员的类 /结构体将默认被处理为 8字节对齐。如果 32位和 64位代码 共用磁盘上的结构体, 需要确保两种体系结构下的结构体的对齐一致。 大多数编译器提供了调整 结构体对齐的方案。 gc中可使用 _attribute_(packed), MSVC提供了 #pragmapack()和 _declspec(align()(译者注,解决方案的项目属性里也可以直接设置) 。4)创建 64位常量时使用 LL或 ULL作为后缀,如:int64_tmy_value=0x123456789L;uint64_tmy_mask=3
41、UL48;5)如果你确实需要 32位和 64位系统具有不同代码,可以在代码变量前使用。(尽量不要这么做,使用时尽量使修改局部化)。 14.预处理宏(预处理宏(预处理宏(预处理宏(PreprocesorMacros)使用宏时要谨慎,尽量以内联函数、枚举和常量代替之。类型 不要使用 使用 备注void*( 或 其 他 指 针 类 型 ) %lx%pint64_t%qd,%lld%“PRId64“uint64_t%qu,%llu,%llx%“PRIu64“,%“PRIx64“size_t%u%“PRIuS“,%“PRIxS“C9指定 %zuptrdiff_t%d%“PRIdS“9指定 %zd宏意味着
42、你和编译器看到的代码是不同的, 因此可能导致异常行为, 尤其是当宏存在于全局作 用域中。 值得庆幸的是, C+中, 宏不像 C中那么必要。宏内联 效率关键代码( performance-critcalcode) 可以内联函数替代; 宏 存储常量可以 const变量替代; 宏 “缩写 ”长变量名可以引用替 代 ;使用宏进行条件编译,这个 ,最好不要这么做,会令测试更加痛苦( #define防止头文件重包含当然是个例外)。 宏可以做一些其他技术无法实现的事情, 在一些代码库 (尤其是底层库中) 可以看到宏的某些 特性 ( 如 字符串化( stringifying, 译 者注, 使用 #) 、 连接
43、 ( concatenation, 译 者注, 使用 #)等等)。但在使用前,仔细考虑一下能不能不使用宏实现同样效果。译 者 注 : 关 于 宏 的 高 级 应 用 , 可以 参 考 C语 言 宏 的 高 级 应 用 。下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考: 1)不要在 .h文件中定义宏;2)使用前正确 #define,使用后正确 #undef;3)不要只是对已经存在的宏使用 #undef,选择一个不会冲突的名称;4)不使用会导致不稳定的 C+构造( unbalncedC+constructs,译者注) 的宏,至少文档说明其行为。 15.0和和和和NUL(0andNUL)
44、整数用 0,实数用 0.0,指针用 NULL,字符(串)用 0。整数用 0,实数用 0.0,这一点是毫无争议的。对于指针 (地址值) , 到 底是用 0还是 NULL, BjarneStroustrup建议使用最原始的 0, 我 们建议使用看上去像是指针的 NULL, 事实上一些 C+编译器 (如 gc4.10) 专门提供了 NULL的定义,可以给出有用的警告,尤其是 sizeof(NULL)和 sizeof(0)不相等 的情况。字符(串)用 0,不仅类型正确而且可读性好。16.sizeof(sizeof)尽可能用 sizeof(varname)代替 sizeof(type)。使用 sizeo
45、f(varname)是因为当变量类型改变时代码自动同步, 有 些情况下 sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。 Structdat;meset(meset(17.Bost库(库(库(库(Bost)只使用 Bost中被认可的库。定 义 : Bost库集是一个非常受欢迎的、 同级评议的 ( per-eviwed) 、 免费的、 开 源的 C+库。 优 点 : Bost代码质量普遍较高、 可移植性好, 填补了 C+标准库很多空白, 如 型别特性 ( typetraits) 、更完善的 绑定( binders) 、更好的智能指针,同时还提供了 TR1( 标
46、准 库 的 扩 展 )的实现。缺 点 : 某些 Bost库提倡的编程实践可读性差,像 元程序( metaprograming) 和其他高 级模板技术,以及过度 “函数化 ”( “functional“) 的编程风格。结 论 :为了向阅读和维护代码的人员提供更好的可读性,我们只允许使用 Bost特性的一个成熟子集,当前,这些库包括:1)CompresdPair: boost/compressed_pair.hpp;2)PointerContainer: boost/ptr_container不包括 ptr_array.hpp和序列化( serializti)。我们会积极考虑添加可以的 Bost特
47、性,所以不必拘泥于该规则。_译者:关于 C+特性的注意事项,总结一下:1.对 于 智 能 指 针 , 安 全 第 一 、 方 便 第 二 ,尽 可 能 局 部 化 ( scoped_ptr) ;2.引 用 形 参 加 上 const, 否 则 使 用 指 针 形 参 ;3.函 数 重 载 的 使 用 要 清 晰 、 易 读 ;4.鉴 于 容 易 误 用 , 禁 止 使 用 缺 省 函 数 参 数( 值 得 商 榷 ) ;5.禁 止 使 用 变 长 数 组 ;6.合 理 使 用 友 元 ;7.为 了 方 便 代 码 管 理 , 禁 止 使 用 异 常 ( 值得 商 榷 ) ;8.禁 止 使 用
48、RTI, 否 则 重 新 设 计 代 码 吧 ;9.使 用 C+风 格 的 类 型 转 换 , 除 单 元 测 试 外 不 要 使 用 dynamic_ast;10.使 用 流 还 printf+read/write, itisaproble;1.能 用 前 置 自 增 /减 不 用 后 置 自 增 /减 ;12.const能 用 则 用 , 提 倡 const在 前 ;13.使 用 确 定 大 小 的 整 型 , 除 位 组 外 不 要 使用 无 符 号 型 ;14.格 式 化 输 出 及 结 构 对 齐 时 , 注 意 32位 和 64位 的 系 统 差 异 ;15.除 字 符 串 化 、
49、 连 接 外 尽 量 避 免 使 用 宏 ;16.整 数 用 0, 实 数 用 0., 指 针 用 NUL, 字 符 ( 串 ) 用 0;17.用 sizeof(varname)代 替 sizeof(type);18.只 使 用 Bost中 被 认 可 的 库 。G ogleG G G C+编 程 风 格 指 南 ( 五 )命名约定命名约定命名约定命名约定最重要的一致性规则是命名管理,命名风格直接可以直接确定命名实体是:类型、变量、函数、 常量、宏等等,无需查找实体声明,我们大脑中的模式匹配引擎依赖于这些命名规则。 命名规则具有一定随意性, 但 相比按个人喜好命名, 一 致性更重要, 所 以不管你怎么想, 规 则 总归是规则。 1.通用命名规则(通用命名规则(通用命名规则(通用命名规则(GenralNamingRules)函数命名、 变量命名、 文件命名应具有描述性, 不要过度缩写, 类型和变量应该是名词, 函数 名可以用 “命令性 ”动词。如 何 命 名 :尽可能给出描述性名