GoogleCpp风格指南 5) 其他特性_part2
2015-02-27 14:03
148 查看
5.10前置自增和自减PreincrementandPredecrement
Tip对于迭代器iterator和其他模板对象templateobject使用前缀形式(++i)的自增,自减运算符;
定义:
对于变量在自增(++i或i++)或自减(--i或i--)后,表达式的值没有被用到的情况下,需要确定到底是使用前置还是后置的自增(自减);
优点:
不考虑返回值的话,前置pre自增(++i)通常要比后置post自增(i++)效率更高;因为后置自增(自减)需要对表达式的值i进行一次拷贝;如果i是迭代器或其他非数值non-scalar类型,拷贝的代价是比较大的;既然两种自增方式实现的功能一样,为什么不总是使用前置自增呢?
缺点:
在C开发中,当表达式的值未被使用时,传统的做法还是使用后置自增,特别是在for循环中;有些人觉得后置自增更加易懂,因为这很像自然语言,主语subject(i)在谓语动词precede(++)前;
[C语言中没有class类型,基本上POD不必在意前置或后置的效率区别]
结论:
对简单数值scalar(非对象non-object),两种都无所谓;对迭代器和模板类型,使用前置自增(自减);
5.11const的使用Useofconst
Tip强烈建议在任何可能的情况下都要使用const;[Add]c++11中,constexpr对于某些const使用情况是更好的选择;[http://en.cppreference.com/w/cpp/language/constexpr]
<<<
定义:
在声明的变量或参数前preceded加上关键字const用于指明变量值不可被篡改(如constintfoo);为类中的函数加上const限定符qualifier表明该函数不会修改类成员变量的状态(如classFoo{intBar(charc)cosnt;};)
优点:
大家更容易理解如何使用变量;编译器可以更好地进行类型检测;相应地,也能生成更好的代码;人们对编写正确的代码更加自信,因为他们知道所调用的函数被限定了能或不能修改变量值;即使是在无锁的多线程编程中withoutlocksinmulti-threaded,人们也知道什么样的函数是安全的;
缺点:
const是入侵性viral的:如果你向一个函数传入const变量,函数原型声明中也必须对应const参数(否则变量需要const_cast类型转换),在调用库函数是显得尤其麻烦;
结论:
const变量,数据成员,函数和参数为编译时类型检测增加了一层保障:便于尽早发现错误;因此,我们强烈建议在任何可能的情况下使用const;
-如果函数不会修改传入的引用或指针类型参数,该参数应声明为const;
-尽可能将函数声明为const;访问函数几乎总是const;其他不会修改任何数据成员,没有调用非const函数,不会返回数据成员非const指针或引用的函数也应声明成const;
-如果数据成员在对象构造之后不再发生变化,可将其定义为const;
[Remove]然而,也不要发疯似的使用const;像constint*const*constx;就有些过了,虽然它非常精确地描述了常量x;关注真正有帮助一样的信息:前面的例子写成constint**x就够了;[内容不可变]<<<
关键字mutable可以使用,但是在多线程中是不安全的,使用时首先要考虑线程安全性;
const的位置Wheretoputtheconst:
有人喜欢intconst*foo形式,不喜欢constint*foo;他们认为前者更一致因此可读性也更好:遵循了const总位于其描述的对象之后的原则;但是一致性原则不适用于此,由于多数const表达式只有一个const,而且应用的是一个值,很少有深层嵌套的指针表达式fewdeeply-nestedpointerexpressions;"不要过度使用"的声明可以取消大部分你原本想保持的一致性;
将const放在前面才更易读,因为在自然语言中形容词adjective(const)是在名词noun(int)之前;
这是说,我们提倡但不强制const在前;但要保持代码的一致性;(译注,就是不要在一些地方把const写在类型前面,在其他地方又写在后面,要确定一种写法,然后保持一致);
[Add]
使用constexprUseofconstexpr
C++11中,使用constexpr来定义true的常量或确保常量初始化constantinitialization;
定义:
一些变量可以被声明为constexpr,表明变量是true的常量,e.g.在编译/链接时是固定的;一些函数和cotr可以被声明为constexpr,让它们可以在定义一个constexpr变量时被使用;
优点:
使用constexpr定义浮点数floating-point表达式常量,而不是字面量literal定义,用户定义的类型的定义和函数调用functioncall的常量的定义;
缺点:
把某些东西定义为constexpr可能会在之后导致一些迁移migration问题,或许不得不回退回去downgraded;目前的对于constexpr函数和ctor的限制规定restriction可能会在这些定义中产生些隐晦的替代方案workaround;
结论:
constexpr定义可以在一些接口的const部分给出健壮的规格robustspecification;使用constexpr来指定真实常量trueconstants以及支持函数的定义;使用constexpr来防止复杂的函数定义;不要使用constexpr来强制内联inline;[http://stackoverflow.com/questions/14391272/does-constexpr-imply-inline]
<<<
5.12整型IntegerTypes
TipC++内建整型中,仅使用int;如果程序中需要不同大小的变量,可以使用<stdint.h>中长度精确precise-width的整型,如int16_t;[http://www.cplusplus.com/reference/cstdint/]
[Add]如果你的变量表示了一个可以变大或者等于2^31(2GiB)的值,使用一个64-bit类型,比如int64_t;记住即使你的值对于int来说不会过大,它还是可能在一些中间计算中需要一个更大的类型;如果不确定,就选用更大的类型;<<<
定义:
C++没有指定整型的大小;通常人们假定short是16位,int是32位,long是32位,longlong是64位;(bits)
优点:
保持声明统一性Uniformity;
缺点:
C++中整型大小因编译器和体系结构architecture的不同而不同;
结论:
<stdint.h>定义了int16_t,uint32_t,int64_t等整型,在需要确保guarantee整型大小时可以优先preference使用它们代替short,unsignedlonglong等;在C整型中,只使用int;在合适的情况下,推荐使用标准类型如size_t和ptrdiff_t;
如果已知整数不会太大,我们常常会使用int,如循环计数loopcounter;在类似的情况下使用原生类型plainoldint;你可以认为int至少为32位,但不要认为它会多于32位;如果需要64位整型,用int64_t或uint64_t;
对于大整数,使用int64_t;
不要使用uint32_t等无符号整型,除非有正当valid理由,比如在表示一个位组bitpattern而不是一个数值,或是需要定义二进制补码溢出overflowmodulo2^N;尤其是不要为了指出数值永远不会为负,而使用无符号类型;相反,你应该用断言来保护数据;
<<<
[Add]如果你的代码是一个返回大小的容器,确保使用一个可适应accommodate任何使用的可能性的数据类型;不确定的时候使用一个更大的类型而不是较小的类型;
当转化整型的时候要小心;整型转换和提升promotion会引起一些反直觉non-intuitive的行为;
<<<
关于无符号整数OnUnsignedIntegers:
有些人,包括一些教科书作者,推荐使用无符号整型表示非负数;这种做法试图达到自我文档化self-documentation;但是在C语言中,这一优点被由其导致的bug所淹没outweighed;看看下面的例子:
上述循环永远不会退出!有时gcc会发现该bug并报警,但大部分情况下都不会;类似的bug还会出现在比较有符号变量和无符号变量时;主要是C的类型提升机制会导致无符号类型的行为出乎你的意料;
因此,使用断言来指出变量为非负数,而不要使用无符号类型;
5.1364位下的可移植性64-bitPortability
Tip代码应该对64位和32位系统友好;处理打印printing,比较comparison,结构体对齐structurealignment时应该切记;
-对于某些类型,printf()的指示符在32位和64位系统上可移植性不是很好;C99标准定义了一些可移植的格式化指示符;不幸的是,MSVC7.1并非全部支持;而且标准中也有所遗漏,所以有时我们不得不自己定义一个丑陋的版本(头文件inttype.h仿标准风格):
checktable:
NotePRI*宏会被编译器扩展concatenated为独立字符串;因此如果使用非常量的格式化字符串,需要将宏的值而不是宏名插入格式中;使用PRI*宏同样可以在%后包含长度指示符,etc;例如:printf("x=%30"PRIuS"\n",x);在32位Linux将被展开为:printf("x=%30""u""\n",x);编译器当成:printf("x=%30u\n",
x);处理;(译注:这在MSVC6.0上不行,VC6编译器不会自动把引号间隔的多个字符串连接成一个长字符串);
-记住sizeof(void*)!=sizeof(int);如果需要一个指针大小的整数要用intptr_t;[不同编译器,系统不一样]
-要非常小心地对待结构体对齐,尤其是要持久化存储到磁盘上的结构体;(译注:持久化--将数据按字节流顺序保存在磁盘文件或数据库中);在64位系统中,任何含有int64_t/uint64_t成员的类/结构体,缺省都以8字节在结尾对齐;如果32位和64位代码要共用持久化在磁盘上的结构体;需要确保两种体系结构下的结构体对齐一致;(packed)大多数编译器都允许调整结构体对齐;
gcc中可使用__attribute__((packed));MSVC则提供了#pragmapack()和__deslspec(align());
(译注:解决方案的项目属性栏里也可以直接设置)[VS的项目--solution]
[http://en.wikipedia.org/wiki/Data_structure_alignment]
-创建64位常量时使用LL或ULL作为后缀suffixes,如:
-如果你确实需要32位和64位系统具有不同代码,可以使用#ifdef_LP64指令在代码变量中区分32/64位代码;(尽量不要这么做,如果非用不可,尽量使修改局部化);
[http://stackoverflow.com/questions/685124/how-to-identify-a-64-bit-build-on-linux-using-the-preprocessor
]
5.14预处理宏PreprocessorMacros
Tip使用宏时要非常谨慎,尽量以内联函数,枚举和常量替代之;
宏意味着你和编译器看到的代码是不同的;这可能会导致异常行为,尤其因为宏具有全局作用域;
值得庆幸的是,C++中,宏不像在C中那么必不可少;以往用宏展开性能关键performance-critical的代码,现在可以用内联函数替代;用宏表示的常量可以被const变量代替;用宏"缩写"长变量的别名abbreviate可被引用代替reference;[以及typedef];用宏进行条件编译...这个,千万别这么做,(#define防止头文件重复包含当然是个特例)会令测试更加痛苦;
宏可以做一些其他技术无法实现的事情,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如用#字符串化stringifying,用##连接concatenation等);但在使用前,仔细考虑一下能不能不使用宏达到同样的目的;[Hack:private和public]
下面给出的用法模式可以避免使用宏带来的问题;如果要用宏,尽可能遵守:
-不要在.h文件中定义宏;
-在马上要使用时才进行#define,使用完要立即#undefine;
-不要只是对已经存在的宏使用#undef,选择一个不会冲突的独特名称;
-不要试图使用展开后会导致C++构造不稳定的宏,否则至少要附上文档说明其行为;[不要在构造相关代码使用宏?]
-最好不要使用##来产生function/class/variable的名字;
5.150andnullptr/NULL
Tip整数用0,实数用0.0,指针用NULL或nullptr,字符chars(串)用'\0';
整数用0,实数用0.0,这一点毫无争议controversial;
对于指针(地址值),到底是使用0还是NULL/nullptr,
[Remove]BjarneStroustrup建议使用最原始的0;我们建议使用看上去像是指针的NULL;[C++11:nullptr]<<<
[Add]对允许C++11的项目,使用nullptr,C++03项目使用NULL,看起来比较像个指针;<<<
事实上一些C++编译器(如gcc4.1.0)对NULL进行了特殊的定义,可以给出有用的警告信息,尤其是sizeof(NULL)和sizeof(0)不相等的情况;
字符(串)用'\0',不仅类型正确而且可读性好;[http://bbs.csdn.net/topics/390615761]
5.16sizeof
Tip尽可能用sizeof(varname)代替sizeof(type);
使用sizeof(varname)是因为当代码中变量类型改变时会自动更新;某些特定情况下sizeof(type)或许有意义,比如管理external或internal数据类型变量的代码,而没有方便的C++类型;但还是要尽量避免,因为它会导致变量类型改变后不能同步;
WARNING
[Add]
Other
<<<
[Add]
auto
使用auto来避免类型名字杂乱clutter;当有助于可读性的时候继续使用明显的manifest类型声明,除了本地local变量之外不要使用auto;
定义:
C++11中,一个由auto指定类型的变量会给出符合初始化它的表达式的类型;可以使用auto来用copy初始化initialize它,或者绑定bind一个引用;
[http://en.cppreference.com/w/cpp/language/auto]
优点:
C++类型名称有时候很长而且笨重cumbersome,特别是在模板或名字空间中:
返回值很难读懂,蒙蔽obscure了语句的原本意图;改为:
更易读懂;
没有auto的话我们有时不得不将一个类型名字在一个表达式内写两遍,对于阅读者来说没有意义:
使用auto让中间intermediate变量的使用更合理,减少了显式书写类型的负担;
缺点:
有时候变量是manifest的会更清晰,特别是变量初始化依赖于我们之前声明的东西;像这样的表达式:
如果x是几百行之前声明的,i的类型可能不够明显;
程序员不得不去理解auto和constauto&之间的区别,有时候他们会在没有意识到的时候拿到一份copy;
auto和C++11brace-initialization之间的交互interaction可能会令人混淆;声明:
它们表示不同的东西,x是个int,但y是一个std::initializer_list<int>;相同情况会发生在其他普通隐式代理normally-invisibleproxy类型上;
如果一个auto变量被用作接口的一部分,e.g.在头文件中作为一个const,程序员可能只是为了改变它的值而改变它的类型,导致没有想到的一系列API的彻底radical改变;
结论:
auto只对本地变量开放;不要在文件范围file-scopye或名字空间范围namespace-scope中对变量,或类成员使用auto;永远不要用大括号初始化列表bracedinitializerlist初始化一个auto类型auto-typed变量;
auto关键字也用在C++feature无关的地方:它作为一种新的函数声明的语法的一部分,尾随返回值类型trailingreturntype;trailingreturntype只在lambda表达式中被允许使用;
BracedInitializerList
bracedinitializerlists;[http://en.cppreference.com/w/cpp/language/list_initialization]
在C++03,聚合类型aggregatetype(没有ctor的数组和结构体)可以用bracedinitializerlist初始化;
C++11中,这个语法被普遍化generalized了,任何一个对象类型都可以用bracedinitializerlist来初始化,作为一个braced-init-list的C++语法grammar;这里有几个例子:
一个用户定义的类型也可以使用std::initializer_list<T>[http://en.cppreference.com/w/cpp/utility/initializer_list]定义一个ctor或assignmentoperator,会从braced-init-list自动创建;
最后braceinitialization也能调用普通的数据类型的ctor,即使没有std::initializer_list<T>构造函数;
Note永远不要把一个braced-init-list分配给一个auto的本地变量;在单个元素的case中,其意义可能会混淆:
Bad:
Good:
参见BracedInitializerListFormat;
<<<
---TBC---YCR
Tip对于迭代器iterator和其他模板对象templateobject使用前缀形式(++i)的自增,自减运算符;
定义:
对于变量在自增(++i或i++)或自减(--i或i--)后,表达式的值没有被用到的情况下,需要确定到底是使用前置还是后置的自增(自减);
优点:
不考虑返回值的话,前置pre自增(++i)通常要比后置post自增(i++)效率更高;因为后置自增(自减)需要对表达式的值i进行一次拷贝;如果i是迭代器或其他非数值non-scalar类型,拷贝的代价是比较大的;既然两种自增方式实现的功能一样,为什么不总是使用前置自增呢?
缺点:
在C开发中,当表达式的值未被使用时,传统的做法还是使用后置自增,特别是在for循环中;有些人觉得后置自增更加易懂,因为这很像自然语言,主语subject(i)在谓语动词precede(++)前;
[C语言中没有class类型,基本上POD不必在意前置或后置的效率区别]
结论:
对简单数值scalar(非对象non-object),两种都无所谓;对迭代器和模板类型,使用前置自增(自减);
5.11const的使用Useofconst
Tip强烈建议在任何可能的情况下都要使用const;[Add]c++11中,constexpr对于某些const使用情况是更好的选择;[
<<<
定义:
在声明的变量或参数前preceded加上关键字const用于指明变量值不可被篡改(如constintfoo);为类中的函数加上const限定符qualifier表明该函数不会修改类成员变量的状态(如classFoo{intBar(charc)cosnt;};)
优点:
大家更容易理解如何使用变量;编译器可以更好地进行类型检测;相应地,也能生成更好的代码;人们对编写正确的代码更加自信,因为他们知道所调用的函数被限定了能或不能修改变量值;即使是在无锁的多线程编程中withoutlocksinmulti-threaded,人们也知道什么样的函数是安全的;
缺点:
const是入侵性viral的:如果你向一个函数传入const变量,函数原型声明中也必须对应const参数(否则变量需要const_cast类型转换),在调用库函数是显得尤其麻烦;
结论:
const变量,数据成员,函数和参数为编译时类型检测增加了一层保障:便于尽早发现错误;因此,我们强烈建议在任何可能的情况下使用const;
-如果函数不会修改传入的引用或指针类型参数,该参数应声明为const;
-尽可能将函数声明为const;访问函数几乎总是const;其他不会修改任何数据成员,没有调用非const函数,不会返回数据成员非const指针或引用的函数也应声明成const;
-如果数据成员在对象构造之后不再发生变化,可将其定义为const;
[Remove]然而,也不要发疯似的使用const;像constint*const*constx;就有些过了,虽然它非常精确地描述了常量x;关注真正有帮助一样的信息:前面的例子写成constint**x就够了;[内容不可变]<<<
关键字mutable可以使用,但是在多线程中是不安全的,使用时首先要考虑线程安全性;
const的位置Wheretoputtheconst:
有人喜欢intconst*foo形式,不喜欢constint*foo;他们认为前者更一致因此可读性也更好:遵循了const总位于其描述的对象之后的原则;但是一致性原则不适用于此,由于多数const表达式只有一个const,而且应用的是一个值,很少有深层嵌套的指针表达式fewdeeply-nestedpointerexpressions;"不要过度使用"的声明可以取消大部分你原本想保持的一致性;
将const放在前面才更易读,因为在自然语言中形容词adjective(const)是在名词noun(int)之前;
这是说,我们提倡但不强制const在前;但要保持代码的一致性;(译注,就是不要在一些地方把const写在类型前面,在其他地方又写在后面,要确定一种写法,然后保持一致);
[Add]
使用constexprUseofconstexpr
C++11中,使用constexpr来定义true的常量或确保常量初始化constantinitialization;
定义:
一些变量可以被声明为constexpr,表明变量是true的常量,e.g.在编译/链接时是固定的;一些函数和cotr可以被声明为constexpr,让它们可以在定义一个constexpr变量时被使用;
优点:
使用constexpr定义浮点数floating-point表达式常量,而不是字面量literal定义,用户定义的类型的定义和函数调用functioncall的常量的定义;
缺点:
把某些东西定义为constexpr可能会在之后导致一些迁移migration问题,或许不得不回退回去downgraded;目前的对于constexpr函数和ctor的限制规定restriction可能会在这些定义中产生些隐晦的替代方案workaround;
结论:
constexpr定义可以在一些接口的const部分给出健壮的规格robustspecification;使用constexpr来指定真实常量trueconstants以及支持函数的定义;使用constexpr来防止复杂的函数定义;不要使用constexpr来强制内联inline;[
<<<
5.12整型IntegerTypes
TipC++内建整型中,仅使用int;如果程序中需要不同大小的变量,可以使用<stdint.h>中长度精确precise-width的整型,如int16_t;[
[Add]如果你的变量表示了一个可以变大或者等于2^31(2GiB)的值,使用一个64-bit类型,比如int64_t;记住即使你的值对于int来说不会过大,它还是可能在一些中间计算中需要一个更大的类型;如果不确定,就选用更大的类型;<<<
定义:
C++没有指定整型的大小;通常人们假定short是16位,int是32位,long是32位,longlong是64位;(bits)
优点:
保持声明统一性Uniformity;
缺点:
C++中整型大小因编译器和体系结构architecture的不同而不同;
结论:
<stdint.h>定义了int16_t,uint32_t,int64_t等整型,在需要确保guarantee整型大小时可以优先preference使用它们代替short,unsignedlonglong等;在C整型中,只使用int;在合适的情况下,推荐使用标准类型如size_t和ptrdiff_t;
如果已知整数不会太大,我们常常会使用int,如循环计数loopcounter;在类似的情况下使用原生类型plainoldint;你可以认为int至少为32位,但不要认为它会多于32位;如果需要64位整型,用int64_t或uint64_t;
对于大整数,使用int64_t;
不要使用uint32_t等无符号整型,除非有正当valid理由,比如在表示一个位组bitpattern而不是一个数值,或是需要定义二进制补码溢出overflowmodulo2^N;尤其是不要为了指出数值永远不会为负,而使用无符号类型;相反,你应该用断言来保护数据;
<<<
[Add]如果你的代码是一个返回大小的容器,确保使用一个可适应accommodate任何使用的可能性的数据类型;不确定的时候使用一个更大的类型而不是较小的类型;
当转化整型的时候要小心;整型转换和提升promotion会引起一些反直觉non-intuitive的行为;
<<<
关于无符号整数OnUnsignedIntegers:
有些人,包括一些教科书作者,推荐使用无符号整型表示非负数;这种做法试图达到自我文档化self-documentation;但是在C语言中,这一优点被由其导致的bug所淹没outweighed;看看下面的例子:
1 | for (unsigned int i =foo.Length()-1;i>=0;--i) //... |
因此,使用断言来指出变量为非负数,而不要使用无符号类型;
5.1364位下的可移植性64-bitPortability
Tip代码应该对64位和32位系统友好;处理打印printing,比较comparison,结构体对齐structurealignment时应该切记;
-对于某些类型,printf()的指示符在32位和64位系统上可移植性不是很好;C99标准定义了一些可移植的格式化指示符;不幸的是,MSVC7.1并非全部支持;而且标准中也有所遗漏,所以有时我们不得不自己定义一个丑陋的版本(头文件inttype.h仿标准风格):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //printfmacrosforsize_t,inthestyleofinttypes.h #ifdef_LP64 #define__PRIS_PREFIX"z" #else #define__PRIS_PREFIX #endif //Usethesemacrosaftera%inaprintfformatstring //togetcorrect32/64bitbehavior,likethis: //size_tsize=records.size(); //printf("%"PRIuS"\n",size); #definePRIdS__PRIS_PREFIX"d" #definePRIxS__PRIS_PREFIX"x" #definePRIuS__PRIS_PREFIX"u" #definePRIXS__PRIS_PREFIX"X" #definePRIoS__PRIS_PREFIX"o" |
Type | DONOTuse | DOuse | Notes |
---|---|---|---|
void*(oranypointer) | %lx | %p | |
int64_t | %qd, %lld | %"PRId64" | |
uint64_t | %qu, %llu, %llx | %"PRIu64", %"PRIx64" | |
size_t | %u | %"PRIuS", %"PRIxS" | C99specifies%zu |
ptrdiff_t | %d | %"PRIdS" | C99specifies%td |
x);处理;(译注:这在MSVC6.0上不行,VC6编译器不会自动把引号间隔的多个字符串连接成一个长字符串);
-记住sizeof(void*)!=sizeof(int);如果需要一个指针大小的整数要用intptr_t;[不同编译器,系统不一样]
-要非常小心地对待结构体对齐,尤其是要持久化存储到磁盘上的结构体;(译注:持久化--将数据按字节流顺序保存在磁盘文件或数据库中);在64位系统中,任何含有int64_t/uint64_t成员的类/结构体,缺省都以8字节在结尾对齐;如果32位和64位代码要共用持久化在磁盘上的结构体;需要确保两种体系结构下的结构体对齐一致;(packed)大多数编译器都允许调整结构体对齐;
gcc中可使用__attribute__((packed));MSVC则提供了#pragmapack()和__deslspec(align());
(译注:解决方案的项目属性栏里也可以直接设置)[VS的项目--solution]
[
-创建64位常量时使用LL或ULL作为后缀suffixes,如:
1 2 | int64_tmy_value=0x123456789LL; uint64_tmy_mask=3ULL<<48; |
[
1 2 3 | #ifdefined(__LP64__)||defined(_LP64) #defineBUILD_641 #endif |
5.14预处理宏PreprocessorMacros
Tip使用宏时要非常谨慎,尽量以内联函数,枚举和常量替代之;
宏意味着你和编译器看到的代码是不同的;这可能会导致异常行为,尤其因为宏具有全局作用域;
值得庆幸的是,C++中,宏不像在C中那么必不可少;以往用宏展开性能关键performance-critical的代码,现在可以用内联函数替代;用宏表示的常量可以被const变量代替;用宏"缩写"长变量的别名abbreviate可被引用代替reference;[以及typedef];用宏进行条件编译...这个,千万别这么做,(#define防止头文件重复包含当然是个特例)会令测试更加痛苦;
宏可以做一些其他技术无法实现的事情,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如用#字符串化stringifying,用##连接concatenation等);但在使用前,仔细考虑一下能不能不使用宏达到同样的目的;[Hack:private和public]
下面给出的用法模式可以避免使用宏带来的问题;如果要用宏,尽可能遵守:
-不要在.h文件中定义宏;
-在马上要使用时才进行#define,使用完要立即#undefine;
-不要只是对已经存在的宏使用#undef,选择一个不会冲突的独特名称;
-不要试图使用展开后会导致C++构造不稳定的宏,否则至少要附上文档说明其行为;[不要在构造相关代码使用宏?]
-最好不要使用##来产生function/class/variable的名字;
5.150andnullptr/NULL
Tip整数用0,实数用0.0,指针用NULL或nullptr,字符chars(串)用'\0';
整数用0,实数用0.0,这一点毫无争议controversial;
对于指针(地址值),到底是使用0还是NULL/nullptr,
[Remove]BjarneStroustrup建议使用最原始的0;我们建议使用看上去像是指针的NULL;[C++11:nullptr]<<<
[Add]对允许C++11的项目,使用nullptr,C++03项目使用NULL,看起来比较像个指针;<<<
事实上一些C++编译器(如gcc4.1.0)对NULL进行了特殊的定义,可以给出有用的警告信息,尤其是sizeof(NULL)和sizeof(0)不相等的情况;
字符(串)用'\0',不仅类型正确而且可读性好;[
5.16sizeof
Tip尽可能用sizeof(varname)代替sizeof(type);
使用sizeof(varname)是因为当代码中变量类型改变时会自动更新;某些特定情况下sizeof(type)或许有意义,比如管理external或internal数据类型变量的代码,而没有方便的C++类型;但还是要尽量避免,因为它会导致变量类型改变后不能同步;
1 2 | Structdata; memset (&data, 0, sizeof (data)); |
1 | memset (&data, 0, sizeof (Struct)); |
Other
1 2 3 4 | if (raw_size < sizeof ( int )) { LOG(ERROR) << "compressedrecordnotbigenoughforcount:" << raw_size; return false ; } |
[Add]
auto
使用auto来避免类型名字杂乱clutter;当有助于可读性的时候继续使用明显的manifest类型声明,除了本地local变量之外不要使用auto;
定义:
C++11中,一个由auto指定类型的变量会给出符合初始化它的表达式的类型;可以使用auto来用copy初始化initialize它,或者绑定bind一个引用;
[
1 2 3 4 | vector<string>v; ... auto s1 //Makesacopyofv[0]. const auto & //s2isareferencetov[0]. |
C++类型名称有时候很长而且笨重cumbersome,特别是在模板或名字空间中:
1 | sparse_hash_map<string, int >::iterator iter=m.find(val); |
1 | auto iter =m.find(val); |
没有auto的话我们有时不得不将一个类型名字在一个表达式内写两遍,对于阅读者来说没有意义:
1 | diagnostics::ErrorStatus*status= new diagnostics::ErrorStatus( "xyz" ); |
缺点:
有时候变量是manifest的会更清晰,特别是变量初始化依赖于我们之前声明的东西;像这样的表达式:
1 | auto i =x.Lookup(key); |
程序员不得不去理解auto和constauto&之间的区别,有时候他们会在没有意识到的时候拿到一份copy;
auto和C++11brace-initialization之间的交互interaction可能会令人混淆;声明:
1 2 | auto x(3); // Note:parentheses. auto y{3}; // Note:curlybraces. |
如果一个auto变量被用作接口的一部分,e.g.在头文件中作为一个const,程序员可能只是为了改变它的值而改变它的类型,导致没有想到的一系列API的彻底radical改变;
结论:
auto只对本地变量开放;不要在文件范围file-scopye或名字空间范围namespace-scope中对变量,或类成员使用auto;永远不要用大括号初始化列表bracedinitializerlist初始化一个auto类型auto-typed变量;
auto关键字也用在C++feature无关的地方:它作为一种新的函数声明的语法的一部分,尾随返回值类型trailingreturntype;trailingreturntype只在lambda表达式中被允许使用;
BracedInitializerList
bracedinitializerlists;[
在C++03,聚合类型aggregatetype(没有ctor的数组和结构体)可以用bracedinitializerlist初始化;
1 2 | struct Point { int x; int y; }; Pointp={1,2}; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //Vectortakesabraced-init-listofelements. vector<string>v{ "foo" , "bar" }; //Basicallythesame,ignoringsomesmalltechnicalities. //Youmaychoosetouseeitherform. vector<string>v={ "foo" , "bar" }; //Usablewith'new'expressions. auto p = new vector<string>{ "foo" , "bar" }; //Amapcantakealistofpairs.Nestedbraced-init-listswork. map< int , "one" }, "2" }}; //Abraced-init-listcanbeimplicitlyconvertedtoareturntype. vector< int > test_function(){ return {1, //Iterateoverabraced-init-list. for ( int i //Callafunctionusingabraced-init-list. void TestFunction2(vector< int > v){} TestFunction2({1,2,3}); |
1 2 3 4 5 6 7 8 9 10 11 12 13 | class MyType { public : // // MyType(std::initializer_list< int > for ( int i } MyType& operator=(std::initializer_list< int > clear(); for ( int i } }; MyTypem{2,3,5,7}; |
1 2 3 4 5 6 7 8 9 10 11 | double d{1.23}; //CallsordinaryconstructoraslongasMyOtherTypehasno //std::initializer_listconstructor. class MyOtherType { public : explicit MyOtherType(string); MyOtherType( int , string); }; MyOtherTypem={1, "b" }; //Iftheconstructorisexplicit,youcan'tusethe"={}"form. MyOtherTypem{ "b" }; |
Bad:
1 | auto d //disastd::initializer_list<double> |
1 | auto d double {1.23}; // |
<<<
---TBC---YCR
相关文章推荐
- GoogleCpp风格指南 5) 其他特性_part1
- GoogleCpp风格指南 5) 其他特性_part3
- GoogleCpp风格指南 8)格式 _part2
- GoogleCpp风格指南 9)规则特例 10)结束语
- GoogleCpp风格指南 4)Google奇技
- GoogleCpp风格指南 8)格式 _part1
- Google C++编程风格指南(四):智能指针和其他C++特性
- GoogleCpp风格指南 8)格式 _part1
- GoogleCpp风格指南 3)类
- Google C++编程风格指南(四):智能指针和其他C++特性
- [轉載]Google C++編程風格指南(四):智能指針和其他C++特性
- GoogleCpp风格指南 1)头文件 2)作用域
- Google C++编程风格指南(四):智能指针和其他C++特性(转载)
- GoogleCpp风格指南 7)注释
- Google C++编程风格指南(四):智能指针和其他C++特性
- Google C++编程风格指南(四):智能指针和其他C++特性
- GoogleCpp风格指南 6)命名约定
- PYTHON风格规范——Google 开源项目风格指南
- Google编程风格指南(一):头文件相关
- Google 开源项目风格指南阅读笔记(C++版)