您的位置:首页 > 编程语言 > C语言/C++

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_tptrdiff_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)
//...


上述循环永远不会退出!有时gcc会发现该bug并报警,但大部分情况下都不会;类似的bug还会出现在比较有符号变量和无符号变量时;主要是C的类型提升机制会导致无符号类型的行为出乎你的意料;

因此,使用断言来指出变量为非负数,而不要使用无符号类型;

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"


checktable:
TypeDONOTuseDOuseNotes
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
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位常量时使用LLULL作为后缀suffixes,如:

1

2
int64_tmy_value=0x123456789LL;


uint64_tmy_mask=3ULL<<48;


-如果你确实需要32位和64位系统具有不同代码,可以使用#ifdef_LP64指令在代码变量中区分32/64位代码;(尽量不要这么做,如果非用不可,尽量使修改局部化);

[http://stackoverflow.com/questions/685124/how-to-identify-a-64-bit-build-on-linux-using-the-preprocessor
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',不仅类型正确而且可读性好;[http://bbs.csdn.net/topics/390615761]

5.16sizeof

Tip尽可能用sizeof(varname)代替sizeof(type);

使用sizeof(varname)是因为当代码中变量类型改变时会自动更新;某些特定情况下sizeof(type)或许有意义,比如管理external或internal数据类型变量的代码,而没有方便的C++类型;但还是要尽量避免,因为它会导致变量类型改变后不能同步;
1

2
Structdata;


memset
(&data,
0,
sizeof
(data));


WARNING
1
memset
(&data,
0,
sizeof
(Struct));


[Add]

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一个引用;

[http://en.cppreference.com/w/cpp/language/auto]
1

2

3

4
vector<string>v;


...


auto
s1
=v[0];
//Makesacopyofv[0].


const
auto
&
s2=v[0];
//s2isareferencetov[0].


优点:

C++类型名称有时候很长而且笨重cumbersome,特别是在模板或名字空间中:
1
sparse_hash_map<string,
int
>::iterator
iter=m.find(val);


返回值很难读懂,蒙蔽obscure了语句的原本意图;改为:
1
auto
iter
=m.find(val);


更易读懂;

没有auto的话我们有时不得不将一个类型名字在一个表达式内写两遍,对于阅读者来说没有意义:
1
diagnostics::ErrorStatus*status=
new
diagnostics::ErrorStatus(
"xyz"
);


使用auto让中间intermediate变量的使用更合理,减少了显式书写类型的负担;

缺点:

有时候变量是manifest的会更清晰,特别是变量初始化依赖于我们之前声明的东西;像这样的表达式:
1
auto
i
=x.Lookup(key);


如果x是几百行之前声明的,i的类型可能不够明显;

程序员不得不去理解auto和constauto&之间的区别,有时候他们会在没有意识到的时候拿到一份copy;

auto和C++11brace-initialization之间的交互interaction可能会令人混淆;声明:
1

2
auto
x(3);
//
Note:parentheses.


auto
y{3};
//
Note:curlybraces.


它们表示不同的东西,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初始化;
1

2
struct
Point
{
int
x;
int
y;
};


Pointp={1,2};


C++11中,这个语法被普遍化generalized了,任何一个对象类型都可以用bracedinitializerlist来初始化,作为一个braced-init-list的C++语法grammar;这里有几个例子:
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
,
string>m={{1,
"one"
},
{2,
"2"
}};


//Abraced-init-listcanbeimplicitlyconvertedtoareturntype.


vector<
int
>
test_function(){
return
{1,
2,3};}


//Iterateoverabraced-init-list.


for
(
int
i
:{-1,-2,-3}){}


//Callafunctionusingabraced-init-list.


void
TestFunction2(vector<
int
>
v){}


TestFunction2({1,2,3});


一个用户定义的类型也可以使用std::initializer_list<T>[http://en.cppreference.com/w/cpp/utility/initializer_list]定义一个ctor或assignmentoperator,会从braced-init-list自动创建;
1

2

3

4

5

6

7

8

9

10

11

12

13
class
MyType
{


public
:


//
std::initializer_listreferencestheunderlyinginitlist.


//
Itshouldbepassedbyvalue.


MyType(std::initializer_list<
int
>
init_list){


for
(
int
i
:init_list)append(i);


}


MyType&
operator=(std::initializer_list<
int
>
init_list){


clear();


for
(
int
i
:init_list)append(i);


}


};


MyTypem{2,3,5,7};


最后braceinitialization也能调用普通的数据类型的ctor,即使没有std::initializer_list<T>构造函数;
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"
};


Note永远不要把一个braced-init-list分配给一个auto的本地变量;在单个元素的case中,其意义可能会混淆:

Bad:
1
auto
d
={1.23};
//disastd::initializer_list<double>


Good:
1
auto
d
=
double
{1.23};
//
Good--disadouble,notastd::initializer_list.


参见BracedInitializerListFormat;

<<<

---TBC---YCR
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: