资源描述
More Effecitve C++
作者 : Scott Meyers
译序、导读 : 侯捷
Item 1~28 : ZHC
Item 29~35 : WQ
附1 : 侯捷
附2 : WQ
附3、附4 : 陈崴
1. 译序(侯捷) 3
2. 导读 4
2.1 本书所谈的C++ 4
2.2 惯例与术语 6
2.3 臭虫报告,意见提供,内容更新 7
3. 基础议题 8
3.1 Item M1:指针与引用的区别 8
3.2 Item M2:尽量使用C++风格的类型转换 10
3.3 Item M3:不要对数组使用多态 14
3.4 Item M4:避免无用的缺省构造函数 16
4. 运算符 20
4.1 Item M5:谨慎定义类型转换函数 21
4.2 Item M6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别 27
4.3 Item M7:不要重载“&&”,“||”, 或“,” 29
4.4 Item M8:理解各种不同含义的new和delete 32
5. 异常 37
5.1 Item M9:使用析构函数防止资源泄漏 38
5.2 Item M10:在构造函数中防止资源泄漏 42
5.3 Item M11:禁止异常信息(exceptions)传递到析构函数外 51
5.4 Item M12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异 54
5.5 Item M13:通过引用(reference)捕获异常 60
5.6 Item M14:审慎使用异常规格(exception specifications) 64
5.7 Item M15:了解异常处理的系统开销 69
6. 效率 71
6.1 Item M16:牢记80-20准则(80-20 rule) 72
6.2 Item M17:考虑使用lazy evaluation(懒惰计算法) 74
6.3 Item M18:分期摊还期望的计算 81
6.4 Item M19:理解临时对象的来源 85
6.5 Item M20:协助完成返回值优化 87
6.6 Item M21:通过重载避免隐式类型转换 91
6.7 Item M22:考虑用运算符的赋值形式(op=)取代其单独形式(op) 93
6.8 Item M23:考虑变更程序库 96
6.9 Item M24:理解虚拟函数、多继承、虚基类和RTTI所需的代价 98
7. 技巧(TECHNIQUES,又称IDIOMS 或PATTERN) 106
7.1 Item M25:将构造函数和非成员函数虚拟化 107
7.2 Item M26:限制某个类所能产生的对象数量 111
7.3 Item M27:要求或禁止在堆中产生对象 125
7.4 Item M28:灵巧(smart)指针 134
7.5 Item M29:引用计数 149
7.6 Item M30:代理类 177
7.7 Item M31:让函数根据一个以上的对象来决定怎么虚拟 190
8. 杂项 212
8.1 Item M32:在未来时态下开发程序 212
8.2 Item M33:将非尾端类设计为抽象类 216
8.3 Item M34:如何在同一程序中混合使用C++和C 226
8.4 Item M35:让自己习惯使用标准C++语言 231
9. 附录 237
9.1 推荐读物 237
9.2 一个auto_ptr的实现实例 241
9.3 在 C++ 中计算物件个数(Objects Counting in C++)译者:陈崴 244
9.4 为智能指标实作 operator->*(Implementing operator->* for Smart Pointers)译者:陈崴 254
1. 译序(侯捷)
C++ 是一个难学易用的语言!
C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based,object-oriented,generic paradigm。
世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。
在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程式库大全有之(The C++ Standard Library),另辟蹊径者有之(Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。
这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致命的吸引力与立竿见影的帮助。Scott Meyers 的Effective C++ 和More Effective C++ 是此类佼佼,Herb Sutter 的Exceptional C++ 则是後起之秀。
这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各种C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在base classes 中使用virtual destructor」、「令operator= 传回*this 的reference」,可能在百科型C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题,例如smart pointers, reference counting, proxy classes,double dispatching, 基本上已属design patterns 的层级!
这些都是经验的累积和心血的结晶。
我很高兴将以下三本极佳书籍,规划为一个系列,以精装的形式呈现给您:
1. Effective C++ 2/e, by Scott Meyers, AW 1998
2. More Effective C++, by Scott Meyers, AW 1996
3. Exceptional C++, by Herb Sutter, AW 1999
不论外装或内容,中文版比其英文版兄弟毫不逊色。本书不但与原文本页页对译,
保留索引,并加上精装、书签条、译注、书籍交叉参考1、完整范例码2、读者服务3。
这套书对於您的程式设计生涯,可带来重大帮助。制作这套书籍使我感觉非常快
乐。我祈盼(并相信)您在阅读此书时拥有同样的心情。
侯捷2000/05/15 于新竹.台湾
jjhou@ccca.nctu.edu.tw
1 Effective C++ 2/e 和More Effective C++ 之中译,事实上是以Scott Meyers 的另一个产品Effective C++ CD 为本,不仅资料更新,同时亦将CD 版中两书之交叉参考保留下来。这可为读者带来旁徵博引时的莫大帮助。
2 书中程式多为片段。我将陆续完成完整的范例程式,并在Visual C++,C++Builder,GNU C++ 上测试。请至侯捷网站()下载。
3 欢迎读者对本书范围所及的主题提出讨论,并感谢读者对本书的任何误失提出指正。
来信请寄侯捷电子信箱(jjhou@ccca.nctu.edu.tw)。
2. 导读
对C++ 程式员而言,日子似乎有点过於急促。虽然只商业化不到10 年,C++ 却俨然成为几乎所有主要电算环境的系统程式语言霸主。面临程式设计方面极具挑战性问题的公司和个人,不断投入C++ 的怀抱。而那些尚未使用C++ 的人,最常被询问的一个问题则是:你打算什么时候开始用C++。C++ 标准化已经完成,其所附带之标准程式库幅员广大,不仅涵盖C 函式库,也使之相形见绌。这么一个大型程式库使我们有可能在不必牺牲移植性的情况下,或是在不必从头撰写常用演算法和资料结构的情况下,完成琳琅满目的各种复杂程式。C++ 编译器的数量不断增加,它们所供应的语言性质不断扩充,它们所产生的码品质也不断改善。C++ 开发工具和开发环境愈来愈丰富,威力愈来愈强大,稳健强固(robust)的程度愈来愈高。商业化程式库几乎能够满足各个应用领域中的写码需求。
一旦语言进入成熟期,而我们对它的使用经验也愈来愈多,我们所需要的资讯也就随之改变。1990 年人们想知道C++ 是什么东西。到了1992 年,他们想知道如何运用它。如今C++ 程式员问的问题更高级:我如何能够设计出适应未来需求的软体?我如何能够改善程式码的效率而不折损正确性和易用性?我如何能够实作出语言未能直接支援的精巧机能?
这本书中我要回答这些问题,以及其他许多类似问题。
本书告诉你如何更具实效地设计并实作C++ 软体:让它行为更正确;面对异常情况时更稳健强固;更有效率;更具移植性;将语言特性发挥得更好;更优雅地调整适应;在「混合语言」开发环境中运作更好;更容易被正确运用;更不容易被误用。简单地说就是如何让软体更好。
本书内容分为35 个条款。每个条款都在特定主题上精简摘要出C++ 程式设计社群所累积的智慧。大部份条款以准则的型式呈现,附随的说明则阐述这条准则为什么存在,如果不遵循会发生什么後果,以及什么情况下可以合理违反该准则。所有条款被我分为数大类。某些条款关心特定的语言性质,特别是你可能罕有使用经验的一些新性质。例如条款9~15 专注於exceptions(就像Tom Cargill, Jack Reeves, Herb Sutter 所发表的那些杂志文章一样)。其他条款解释如何结合语言的不同特性以达成更高阶目标。例如条款25~31 描述如何限制物件的个数或诞生地点,如何根据一个以上的物件型别产生出类似虚拟函式的东西,如何产生smart pointers 等等。其他条款解决更广泛的题目。条款16~24 专注於效率上的议题。不论哪一条款,提供的都是与其主题相关且意义重大的作法。在More Effective C++一书中你将学习到如何更实效更精锐地使用C++。大部份C++ 教科书中对语言性质的大量描述,只能算是本书的一个背景资讯而已。
这种处理方式意味,你应该在阅读本书之前便熟悉C++。我假设你已了解类别(classes)、保护层级(protection levels)、虚拟函式、非虚拟函式,我也假设你已通晓templates 和exceptions 背後的概念。我并不期望你是一位语言专家,所以涉及较罕见的C++ 特性时,我会进一步做解释。
2.1 本书所谈的C++
我在本书所谈、所用的C++,是ISO/ANSI 标准委员会於1997 年11 月完成的C++国际标准最後草案(Final Draft International Standard)。这暗示了我所使用的某些语言特性可能并不在你的编译器(s) 支援能力之列。别担心,我认为对你而言唯一所谓「新」特性,应该只有templates,而templates 如今几乎已是各家编译器的必备机能。我也运用exceptions,并大量集中於条款9~15。如果你的编译器(s) 未能支援exceptions,没什么大不了,这并不影响本书其他部份带给你的好处。但是,听我说,纵使你不需用到exceptions,亦应阅读条款9~15,因为那些条款(及其相关篇幅)检验了某些不论什么场合下你都应该了解的主题。
我承认,就算标准委员会授意某一语言特性或是赞同某一实务作法,并非就保证该语言特性已出现在目前的编译器上,或该实务作法已可应用於既有的开发环境上。一旦面对「标准委员会所议之理论」和「真正能够有效运作之实务」间的矛盾,我便两者都加以讨论,虽然我其实比较更重视实务。由於两者我都讨论,所以当你的编译器(s) 和C++ 标准不一致时,本书可以协助你,告诉你如何使用目前既有的架构来模拟编译器(s) 尚未支援的语言特性。而当你决定将一些原本绕道而行的解决办法以新支援的语言特性取代时,本书亦可引导你。
注意当我说到编译器(s) 时,我使用复数。不同的编译器对C++ 标准的满足程度各不相同,所以我鼓励你在至少两种编译器(s) 平台上发展程式码。这么做可以帮助你避免不经意地依赖某个编译器专属的语言延伸性质,或是误用某个编译器对标准规格的错误阐示。这也可以帮助你避免使用过度先进的编译器技术,例如独家厂商才做得出的某种语言新特性。如此特性往往实作不够精良(臭虫多,要不就是表现迟缓,或是两者兼具),而且C++ 社群往往对这些特性缺乏使用经验,无法给你应用上的忠告。雷霆万钧之势固然令人兴奋,但当你的目标是要产出可靠的码,恐怕还是步步为营(并且能够与人合作)得好。
本书用了两个你可能不甚熟悉的C++ 性质,它们都是晚近才加入C++ 标准之中。某些编译器支援它们,但如果你的编译器不支援,你可轻易以你所熟悉的其他性质来模拟它们。
第一个性质是型别bool,其值必为关键字true 或false。如果你的编译器尚未支援bool,有两个方法可以模拟它。第一个方法是使用一个global enum:enum bool { false, true };
这允许你将参数为bool 或int 的不同函式加以多载化(overloading)。缺点是,内建的「比较运算子(comparison operators)」如==, <, >=, 等等仍旧传回ints。
所以以下程式码的行为不如我们所预期:
void f(int);
void f(bool);
int x, y;
...
f( x < y ); // 呼叫f(int),但其实它应该呼叫f(bool)
一旦你改用真正支援bool 的编译器,这种enum 近似法可能会造成程式行为的
改变。
另一种作法是利用typedef 来定义bool,并以常数物件做为true 和false:
typedef int bool;
const bool false = 0;
const bool true = 1;
这种手法相容於传统的C/C++ 语意。使用这种模拟法的程式,在移植到一个支援有bool 型别的编译器平台之後,行为并不会改变。缺点则是无法在函式多载化(overloading)时区分bool 和int。以上两种近似法都有道理,请选择最适合你的一种。
第二个新性质,其实是四个转型运算子:static_cast, const_cast, dynamic_cast,和reinterpret_cast。如果你不熟悉这些转型运算子,请翻到条款2 仔细阅读其中内容。它们不只比它们所取代的C 旧式转型做得更多,也更好。书中任何时候当我需要执行转型动作,我都使用新式的转型运算子。
C++ 拥有比语言本身更丰富的东西。是的,C++ 还有一个伟大的标准程式库(见条款E49)。我尽可能使用标准程式库所提供的string 型别来取代char* 指标,而且我也鼓励你这么做。string objects 并不比char*-based 字串难操作,它们的好处是可以免除你大部份的记忆体管理工作。而且如果发生exception 的话(见条款9 和10),string objects 比较没有memory leaks(记忆体遗失)的问题。
实作良好的string 型别甚至可和对应的char* 比赛效率,而且可能会赢(条款29 会告诉你个中故事)。如果你不打算使用标准的string 型别,你当然会使用类似string 的其他classes,是吧?是的,用它,因为任何东西都比直接使用char* 来得好。
我将尽可能使用标准程式库提供的资料结构。这些资料结构来自Standard Template Library("STL" — 见条款35)。STL 包含bitsets, vectors, lists, queues,stacks, maps, sets, 以及更多东西,你应该尽量使用这些标准化的资料结构,不要情不自禁地想写一个自己的版本。你的编译器或许没有附STL 给你,但不要因为这样就不使用它。感谢Silicon Graphics 公司的热心,你可以从SGI STL 网站下载一份免费产品,它可以和多种编译器搭配。
如果你目前正在使用一个内含各种演算法和资料结构的程式库,而且用得相当愉快,那么就没有必要只为了「标准」两个字而改用STL。然而如果你在「使用STL」和「自行撰写同等功能的码」之间可以选择,你应该让自己倾向使用STL。记得程式码的重用性吗?STL(以及标准程式库的其他组件)之中有许多码是十分值得重复运用的。
2.2 惯例与术语
任何时候如果我谈到inheritance(继承),我的意思是public inheritance(见条款E35)。如果我不是指public inheritance,我会明白地指明。绘制继承体系图时,我对base-derived 关系的描述方式,是从derived classes 往base classes 画箭头。
例如,下面是条款31 的一张继承体系图:
这样的表现方式和我在Effective C++ 第一版(注意,不是第二版)所采用的习惯不同。现在我决定使用这种最广被接受的继承箭头画法:从derived classes 画往base classes,而且我很高兴事情终能归於一统。此类示意图中,抽象类别(abstract classes,例如上图的GameObject)被我加上阴影而具象类别(concrete classes,例如上图的SpaceShip)未加阴影。
Inheritance(继承机制)会引发「pointers(或references)拥有两个不同型别」的议题,两个型别分别是静态型别(static type)和动态型别(dynamic type)。Pointer或reference 的「静态型别」是指其宣告时的型别,「动态型别」则由它们实际所指的物件来决定。下面是根据上图所写的一个例子:
GameObject *pgo = // pgo 的静态型别是GameObject*,
new SpaceShip; // 动态型别是SpaceShip*
Asteroid *pa = new Asteroid; // pa 的静态型别是Asteroid*,
// 动态型别也是Asteroid*。
pgo = pa; // pgo 的静态型别仍然(永远)是GameObject*,
// 至於其动态型别如今是Asteroid*。
GameObject& rgo = *pa; // rgo 的静态型别是GameObject,
// 动态型别是Asteroid。
这些例子也示范了我喜欢的一种命名方式。pgo 是一个pointer-to-GameObject;pa是一个pointer-to-Asteroid;rgo 是一个reference-to-GameObject。我常常以此方式来为pointer 和reference 命名。
我很喜欢两个参数名称:lhs 和rhs,它们分别是"left-hand side" 和"right-hand side" 的缩写。为了了解这些名称背後的基本原理,请考虑一个用来表示分数(rational numbers)的class:
class Rational { ... };
如果我想要一个用以比较两个Rational objects 的函式,我可能会这样宣告:
bool operator==(const Rational& lhs, const Rational& rhs);
这使我得以写出这样的码:
Rational r1, r2;
...
if (r1 == r2) ...
在呼叫operator== 的过程中,r1 位於"==" 左侧,被系结於lhs,r2 位於"=="右侧,被系结於rhs。
我使用的其他缩写名称还包括:ctor 代表"constructor",dtor 代表"destructor",RTTI 代表C++ 对runtime type identification 的支援(在此性质中,dynamic_cast是最常被使用的一个零组件)。
当你配置记忆体而没有释放它,你就有了memory leak(记忆体遗失)问题。Memory leaks 在C 和C++ 中都有,但是在C++ 中,memory leaks 所遗失的还不只是记忆体,因为C++ 会在物件被产生时,自动呼叫constructors,而constructors 本身可能亦配有资源(resources)。举个例子,考虑以下程式码:
class Widget { ... }; // 某个class — 它是什么并不重要。
Widget *pw = new Widget; // 动态配置一个Widget 物件。
... // 假设pw 一直未被删除(deleted)。
这段码会遗失记忆体,因为pw 所指的Widget 物件从未被删除。如果Widget
constructor 配置了其他资源(例如file descriptors, semaphores, window handles,database locks),这些资源原本应该在Widget 物件被摧毁时释放,现在也像记忆体一样都遗失掉了。为了强调在C++ 中memory leaks 往往也会遗失其他资源,我在书中常以resource leaks 一词取代memory leaks。
你不会在本书中看到许多inline 函式。并不是我不喜欢inlining,事实上我相信inline 函式是C++ 的一项重要性质。然而决定一个函式是否应被inlined,条件十分复杂、敏感、而且与平台有关(见条款E33)。所以我尽量避免inlining,除非其中有个关键点非使用inlining 不可。当你在本书之中看到一个non-inline 函式,并不意味我认为把它宣告为inline 是个坏主意,而只是说,它「是否为inline」与当时讨论的主题无关。
有一些传统的C++ 性质已明白地被标准委员会排除。这样的性质被明列於语言的最後撤除名单,因为新性质已经加入,取代那些传统性质的原本工作,而且做得更好。这本书中我会检视被撤除的性质,并说明其取代者。你应该避免使用被撤除的性质,但是过度在意倒亦不必,因为编译器厂商为了挽留其客户,会尽力保存回溯相容性,所以那些被撤除的性质大约还会存活好多年。
所谓client,是指你所写的程式码的客户。或许是某些人(程式员),或许是某些物(classes 或functions)。举个例子,如果你写了一个Date class(用来表现生日、最後期限、耶稣再次降临日等等),任何使用了这个class 的人,便是你的client。任何一段使用了Date class 的码,也是你的clients。Clients 是重要的。
事实上clients 是游戏的主角。如果没有人使用你写的软体,你又何必写它呢?你会发现我很在意如何让clients 更轻松,通常这会导至你的行事更困难,因为好的软体「以客为尊」。如果你讥笑我太过滥情,不妨反躬自省一下。你曾经使用过自己写的classes 或functions 吗?如果是,你就是你自己的client,所以让clients更轻松,其实就是让自己更轻松,利人利己。
当我讨论class template 或function templates 以及由它们所产生出来的classes或functions 时,请容我保留偷懒的权利,不一一写出templates 和其instantiations(具现体)之间的差异。举个例子,如果Array 是个class template,有个型别参数T,我可能会以Array 代表此template 的某个特定具现体(instantiation),虽然其实Array<T> 才是正式的class 名称。同样道理,如果swap 是个function template,有个型别参数T,我可能会以swap 而非swap<T> 表示其具现体。如果这样的简短表示法在当时情况下不够清楚,我便会在表示template 具现体时加上template 参数。
2.3 臭虫报告,意见提供,内容更新
我尽力让这本书技术精准、可读性高,而且有用,但是我知道一定仍有改善空间。
如果你发现任何错误— 技术性的、语言上的、错别字、或任何其他东西— 请告诉我。我会试著在本书新刷中修正之。如果你是第一位告诉我的人,我会很高兴将你的大名登录到本书致谢文(acknowledgments)内。如果你有改善建议,我也非常欢迎。
我将继续收集C++ 程式设计的实效准则。如果你有任何这方面的想法并愿意与我分享,我会十分高兴。请将你的建议、你的见解、你的批评、以及你的臭虫报告,寄至:
Scott Meyers
c/o Editor-in-Chief, Corporate and Professional Publishing
Addison-Wesley Publishing Company
1 Jacob Way
Reading, MA 01867
U. S. A.
或者你也可以送电子邮件到mec++@。
我维护有一份本书第一刷以来的修订记录,其中包括错误修正、文字修润、以及技术更新。你可以从本书网站取得这份记录,以及其他与本书相关的资讯。你也可以透过anonymous FTP,从 的cp/mec++ 目录中取得它。如果你希望拥有这份资料,但无法上网,请寄申请函到上述地址,我会邮寄一份给你。
这篇序文有够长的,让我们开始正题吧。
3. 基础议题
基础议题。是的,pointers(指针)、references(引用)、casts(类型转换)、arrays(数组)、constructors(构造)- 再沒有比这些更基础的议题了。几乎最简单的C++ 程序也会用到其中大部份特性,而许多程序会用到上述所有特性。
尽管你可能已经十分熟悉语言的这一部份,有时候它们还是会令你吃惊。特别是对那些从C转到C++ 的程序员,因为references, dynamic casts, default constructors 及其它non-C 性质背后的观念,往往带有一股幽暗阴郁的色彩。
這一章描述pointers 和references 的差异,並告诉你它们的适当使用时机。本章介绍新的C++ 类型转换(casts)语法,並解释为什么新式类型转换法比旧式的C类型转换法优越。本章也检验C 的数组概念以及C++ 的多态(polymorphism)概念,並说明为什么將这两者混合运用是不智之举。最后,本章讨论default constructors(默认构造函数)的正方和反方意见,並提出一些建议作法,让你回避语言的束缚(因为在你不需default constructors 的情況下,C++ 也会给你一个。
只要留心下面各条款的各项忠告,你将向著一個很好的目标迈进:你所生产的软件可以清楚而正确地表現出你的设计意图。
3.1 Item M1:指针与引用的区别
指针与引用看上去完全不同(指针用操作符“*”和“->”,引用使用操作符“. ”),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
“但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
char *pc = 0; // 设置指针为空值
char& rc = *pc; // 让引用指向空值
这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生)。应该躲开写出这样代码的人,除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。
因为引用肯定会指向一个对象,在C++里,引用应被初始化。
string& rs; // 错误,引用必须被初始化
string s("xyzzy");
string& rs = s; // 正确,rs指向s
指针没有这样的限制。
string *ps; // 未初始化的指针
// 合法但危险
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
void printDouble(const double& rd)
{
cout << rd; // 不需要测试rd,它
} // 肯定指向一个double值
相反,指针则应该总是被测试,防止其为空:
void printDouble(const double *pd)
{
if (pd) { // 检查是否为NULL
cout << *pd;
}
}
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
string s1("Nancy");
string s2("Clancy");
string& rs = s1; // rs 引用 s1
string *ps = &s1; // ps 指向 s1
rs = s2; // rs 仍旧引用s1,
// 但是 s1的值现在是
// "Clancy"
ps = &s2; // ps 现在指向 s2;
// s1 没有改变
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
vector<int> v(10); // 建立整形向量(vector),大小为10;
// 向量是一个在标准C库中的一个模板(见条款M35)
v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
如果操作符[]返回一个指针,那么后一个语句就得这样写:
*v[5] = 10;
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款M30)
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。
3.2 Item M2:尽量使用C++风格的类型转换
仔细想想地位卑贱的类型转换功能(cast),其在程序设计中的地位就象goto语句一样令人鄙视。但是它还不是无法令人忍受,因为当在某些紧要的关头,类型转换还是必需的,这时它是一个必需品。
不过C风格的类型转换并不代表所有的类型转换功能。
一来它们过于粗鲁,能允许你在任何类型之间进行转换。不过如果要进行更精确的类型转换,这会是一个优点。在这些类型转换中存在着巨大的不同,例如把一个指向const对象的指针(pointer-to-const-object)转换成指向非const对象的指针(pointer-to-non-const-object)(即一个仅仅去除const的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象类型)。传统的C风格的类型转换不对上述两种转换进行区分。(这一点也不令人惊讶,因为C风格的类型转换是为C语言设计的,而不是为C++语言设计的)。
二来C风格的类型转换在程序语句中难以识别。在语法上,类型转换由圆括号和标识符组成,而这些可以用在C++中的任何地方。这使得回答象这样一个最基本的有关类型转换的问题变得很困难:“在这个程序中是否使用了类型转换?”。这是因为人工阅读很可能忽略了类型转换的语句,而利用象grep的工具程序也不能从语句构成上区分出它们来。
C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是, static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,
(type) expression
而现在你总应该这样写:
static_cast<type>(expression)
例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
如果用上述新的类型转换方法,你应该这样写:
double result = static_cast<double>(firstNumber)/secondNumber;
这样的类型转换不论是对人工还是对程序都很容易识别。
static_cast在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个
展开阅读全文