Effective C++读书笔记

第0章       导读

1.      Size_t只是typedef,是C++计算个数(例如char*-based字符串内的字符个数或STL容器内的元素个数等等)时用的某种不带正负号(unsigned)类型。它也是vector,deque和string内的operator[]函数接受的参数类型。

第1章       让自己习惯C++

条款01:视C++为一个语言联邦

1.      今天的C++已经是个多重泛型编程语言,一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。

2.      C++主要的次语言,共用四个:

C

Object-Oriented C++

Template C++

STL

C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

条款02:尽量以const,enum, inline替换#define(宁可以编译器替换预处理器)

1.      如宏定义

#define ASPECT_RETIO 1.653

记号名称ASPECT_RETIO在编译器开始处理源码之前就被预处理器移走了,于是ASPECT_RETIO有可能没有进入记号表(symboltable)内。当你运用此常量获得一个编译错误时,可能会带来困惑,因为这个错误信息会提到1.653而不是ASPECT_RETIO。如果这个常量在一个非你所写的头文件内,更是比较难以查找问题。

解决之道是以一个常量替换上述的宏:

const double AspectRatio = 1.653;

作为一个语言常量,AspectRatio肯定会被编译器看到,当然会进入记号表内。

2.      #define无法创建一个class专属常量,因为#define并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。而const成员变量是可以被封装的。

3.      为确保一个变量称为class专属常量,只有一份实体,必须让它成为一个static成员。

4.      如果需要在类的定义内部使用某个常量,如定义数组的大小,同时又不想让别人获得一个pointer或reference指向这个常量,可以使用enum实现。

5.      对于单纯常量,最好以const对象或enums替换#defines.

6.      对于形似函数的宏,最好改用inline函数替换#define。

条款03:尽可能使用const

1.      着意指出常量指针

char greeting[] = “Hello”;

如果关键字const出现在星号左边,表示被指物是常量;

const char* p = greeting; 等价于char const * p = greeting;

如果出现在星号右边,表示指针自身是常量;            char* const p = greeting;

如果出现在星号两边,表示被指物和指针两者都是常量。  const char* const p = greeting;

2.      STL迭代器的作用像个T*指针。

将迭代器声明为const, 如conststd::vector<int>::iterator iter = vec.begin(),表示iter不得指向不同的东西,但它所指之物还是可以改变的。

如果希望迭代器所指的东西不被改动,即希望STL模拟一个constT*指针,需要将迭代器声明为const_iterator。

3.      Const成员函数,将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。

4.      两个成员函数如果只是常量性(constness)不同,可以被重载。

条款04:确定对象被使用前已先被初始化

1.      永远在使用对象之前先将它初始化

2.      确保每一个构造函数都将对象的每一个成员初始化

3.      C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。较佳的一个写法,就是使用memberinitialization list(成员初始化列表)。

4.      C++有着十分固定的“成员初始化次序”,次序总是相同的:base classes更早于其derivedclasses被初始化,而class内部的成员变量总是以其声明次序被初始化,即使它们在成员初始化列表中以不同的次序出现,也是如此。所以,最好总是以其声明的次序进行初始化。

5.      Static对象,所谓static对象,其寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。Static对象包括global对象;定义于namespace作用域内的对象;在class内、在函数内、以及在file作用域内被声明为static的对象。

6.      函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象称为non-localstatic对象。Static对象的析构函数是在main()结束时被自动调用。

7.      所谓编译单元(translation unit)是指产出单一目标文件(singleobject file)的那些代码。基本上它是单一源码文件加上其所包含的头文件。

8.      如果一个编译单元中的对象需要使用另一个编译单元中的non-localstatic对象,但是,C++对“定义于不同的编译单元内的non-localstatic对象”的初始化相对次序并无明确定义。

9.      一个解决方法是将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。这也是Singleton模式的一个常见实现手法。因为C++保证,函数内的localstatic对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

10.  所以,为避免“跨编译单元之初始化次序”问题,请以local static对象替换non-localstatic对象。

第2章       构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数

1.      编译器自动生成的析构函数是个non-virtual,除非这个class的baseclass自身声明有virtual析构函数。

2.      至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将源对象的每一个non-static成员变量拷贝到目标对象。

3.      至于编译器所生成的copy assignment operator,其行为基本上与copyconstructor如出一辙,但是(1)只有当生出的代码合法;(2)且有适当的机会证明它有意义。否则,编译器会拒绝为class生成operator=。如:

class NameObject

{

public:

Nameobject(std::string& name, const int&value);

                private:

                                std::string&namevalue;

                                constint objvalue;

};

 

std::string newdog(“Solly”);

std::string olddog(“Solly”);

Nameobject p(newdog, 2);

Nameobject s(olddog, 32);

p = s;

赋值之前,p.namevalue和s.namevalue均指向string对象,因为在C++中不允许让引用改变指向其他的对象,所以,编译器拒绝生成默认的copyassignment operator。

如果你打算在一个“内含reference成员”的class内支持赋值操作,必须自己定义copyassignment operator。

4.      对于内含const成员的class,编译器的反应也是拒绝生成copy assignmentoperator。因为更改const成员也是不合法的。

5.      如果base class将copy assignment operator声明为private,同样,编译器的反应也是拒绝为derivedclass生成copyassignment operator。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

1.      可以将copy构造函数或copy assignment操作符声明为private。藉由明确声明一个成员函数,阻止了编译器暗自创建其专属版本;同时令这些函数为private,使得成功阻止人们调用它。

2.      在1的情形下,如果用户企图拷贝对象,编译器会阻挠他。但如果不慎在member函数或friend函数内那么做,轮到连接器发出抱怨。如果想把此错误提前到编译期,需要另外定义类:

class Uncopyable

{

protected:

Uncopyable() { }

                        ~Uncopyable() {}

private:

                        Uncopyable(constUncopyable&);

                        Uncopyable&operator=(const Uncopyable&);

};

为求阻止对象被拷贝,需要做的就是继承Uncopyable。

3.      也可以使用Boost提供的版本,那个class名为noncopyable,是个不错的家伙。0

条款07:为多态基类声明virtual析构函数

1.      当derived class对象经由一个base class指针被删除,而该baseclass带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的derived成分没被销毁。

2.      消除这个问题的做法就是给base class一个virtual析构函数。它会销毁整个对象,包括所有的derivedclass成分。

3.      任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

4.      欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。这份信息通常是一个所谓的vptr(virtualtable pointer)指针指出。Vptr指向一个由函数指针构成的数组,称为vtbl(virtualtable);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。

5.      只有当class内含有至少一个virtual函数,才为它声明virtual析构函数。

6.      Polymorphic(带多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

7.      Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

条款08:别让异常逃离析构函数

1.      如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。因为析构函数吐出异常就是危险,总会带来“过早结束程序”或“发生不明确行为”的风险。

条款09:绝不在构造和析构过程中调用virtual函数

1.      不该在构造函数和析构函数期间调用virtual函数,因为这样的调用不会带来预想的结果。

2.      Base class构造期间(即base class构造函数执行期间),virtual函数绝不会下降到derivedclasses阶层。取而代之的是,对象的作为就像隶属base类型一样。非正式的说法就是:在baseclass构造期间,virtual函数不是virtual函数。

3.      在derived class对象的base class构造期间,对象的类型是base class而不是derivedclass。

4.      如果类有多个构造函数,每个都需执行某些相同的工作,那么避免代码重复的一个优秀的做法是把共同的初始化代码放进一个初始化函数如init内。

条款10:令operator=返回一个reference to *this

1.      为了实现“连锁赋值”(如x = (y = (z = 3))),赋值操作符必须返回一个reference指向操作符的左侧实参。这是为classes实现赋值操作符时应该遵守的协议。

className& operator=(const ClassName& rhs)

{

                        …

                        return *this;

}

适合于标准赋值形式:operator=(constT& rhs),

也适用于所有赋值相关运算:T& operator +=(const T& rhs)


条款11:在operator=中处理“自我赋值”

1. 第一种方法,防止“自我赋值”的做法,是“证同测试(identitytest)”达到“自我赋值”的检验目的:

Widget& Widget::operator = ( const Widget& rhs)

{

                If(this == &rhs)

return *this;

               

                deletepb;

                pb =new Bitmap(*rhs.pb);

                return*this;

}

这个版本可以具备“自我赋值安全性”,但仍然存在异常方面的麻烦。如果”newBitmap”导致异常,Widget最终会持有一个指针指向一块被删除的Bitmap。

2.      第二种方法,让operator = 具备“异常安全性”往往自动获得“自我赋值安全”的回报。

Widget& Widget::operator = (const Widget& rhs)

{

                        bitmap* pOrig =pb;           //记住原先的pb

                        pb = newBitmap(*rhs.pb);      //令pb指向*pb的一个复件

                        deletepOrig;                 //删除原先的pb

                        return *this;

}

现在,如果”newBitmap”抛出异常,pb保持原状。即使没有证同测试,这段代码还是能够处理自我赋值,因为对原bitmap做了一份复件、删除原bitmap、然后指向新制造的那个复件。

3.      第三种方法,使用所谓的copy and swap技术。详情见条款29.这种方法是一个常见而够好的operator=撰写方法,

class Widget

{

                        …

                        voidswap(Widget& rhs);     //交换*this和rhs的数据;

                        …

};

Widget& Widget::operator = (const Widget& rhs)

{

                        Widget  temp(rhs);

                        swap(temp);

                        return *this;

}

条款12:复制对象时勿忘其每一个成分

1.      设计良好的面向对象系统(OO-systems)会将对象的内部封装起来,只留两个函数负责对象拷贝(复制),那便是带着适当名称的copy构造函数和copyassignment operator,我们称之为copying函数。

2.      Copying函数的“编译器生成版”会将被拷对象的所有成员变量都做一份拷贝。

3.      如果你为class添加一个成员变量,必须同时修改copying函数,同时需要修改class的所有构造函数以及任何非标准形式的operator= 。不要以为这是显然的,但很多时候会忘记,而编译器是不会给出警告来提醒你。

4.      如果存在继承关系,子类的copying函数,要同时记得复制基类的成员变量。

class Date { … };

class Customer

{

                                public:

                                                Customer(constCustomer& rhs);

                                                Customer&operator = (const Customer& rhs);

 

                                private:

                                                std::stringname;

                                                datelastTranscation;

};

 

class priorityCustomer : public customer

{

                                public:

                                                …

                                                priorityCustomer(constPriorityCustomer& rhs);

                                                priorityCustomer&operator = (const PriorityCustomer& rhs);

                                                …

                                private:

                                                intpriority;

};

 

PriorityCustomer ::PriorityCustomer(const PriorityCustomer& rhs)

                                :customer(rhs),

      priority(rhs.priority)

{

}

priorityCustomer ::PriorityCustomer& operator = (constPriorityCustomer& rhs)

{

                                Customer::operator= (rhs);

                                                priority= rhs.priority;

                                                return*this;

}

5.      当编写了一个copying函数,请确保

(1)      复制所有的local成员变量;

(2)      调用所有base classes内的适当的copying函数。

6.      不要尝试以某个copying函数实现另一个copying函数,如在copyconstructor中调用copyassignment operator。如果二者有相同的部分,不想出现重复代码,就将共同的部分放进第三个函数中,并由两个copying函数共同调用。

条款13:以对象管理资源

1.      把资源放进对象内,便可倚赖C++的“析构函数自动调用机制”确保资源被释放。

2.      标准库提供的auto_ptr是个“类指针(pointer-like)对象”,即“智能指针”

Std::auto_ptr<Investment> pInv(new Investment());

这个简单的例子示范“以对象管理资源”的两个关键想法:

l 获得资源后立刻放进管理对象(managing object)内。上面的代码做到了“资源获取即初始化(ResourceAcquisition Is Initialization, RAII)”

l 资源对象运用析构函数确保资源被释放。

条款14:在资源管理类中小心coping行为

条款15:在资源管理类中提供对原始资源的访问

1.      通过资源管理类可以防止资源泄露,但许多APIs直接指涉资源,如你获取的是一个shared_ptr:boost::shared_ptr<Investment>pInv(create_investment())管理的资源,但API接口只接受原始指针:

void process(Investment* ptr);

这时候需要将shared_ptr转换成原始指针来调用它,两种方法:显式转换盒隐式转换。

条款17:以独立语句将newed对象置入智能指针

1.      

2.      如果写成这样去调用processWidget:

但是,C++编译器以什么样的次序完成上述事情是不确定的,虽然2和3两步是确定2在3前的,但有可能是先执行2,然后执行1,即调用priority函数,最后执行3。但是如果在执行调用priority函数时,出现异常,则会造成内存泄露。

条款18:让接口容易被正确使用,不易被误用

1.“促进正确使用”的方法包括接口的一致性(如STL每个容器的接口比较一致)。除非有好的理由,否则应该尽量令你的types的行为与内置types一致。避免无端与内置类型不兼容,真正的理由是为了提供行为一致的接口。

2.“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

3.tr1::shared_ptr支持定制型删除器(custom deleter)。这可防范DLL问题,可被用来自动解除互斥锁等等。

条款19:设计class犹如设计type

1.几乎每一个class都要求面对以下问题:

*新type的对象应该如何被创建和销毁?涉及class的构造、析构以及内存分配和释放函数。

*对象的初始化和对象的赋值该有什么样的差别?

*新type的对象如果被passedby value(以值传递),意味着什么?

*什么是新type的“合法值”?

*新type需要配合某个继承图系(inheritancegraph)吗?

*新type需要什么样的转换?

*什么样的操作符合函数对此新type而言是合理的?

*什么样的标准函数应该驳回?那些正是你必须声明为private者。

条款20:宁以pass-by-reference-to-const替换 pass-by-value

1.      pass-by-reference-to-const避免构造新的对象,从而无需构造函数和相应的析构函数,如果该类型还继承于父类,那在构造之前还要调用父类的构造函数,相应还要调用父类的析构函数。

2.      以by reference方式传递参数也可以避免slicing(对象切割)问题。当一个derivedclass对象以byvalue方式传递并被视为一个baseclass对象,baseclass的copyconstructor会被调用,而“造成此对象的行为像个derivedclass对象”的那些特化性质全被切割掉了,仅仅留下一个baseclass对象。解决切割(slicing)问题的方法,就是以byreference-to-const的方式传递参数。

3.      如果窥视C++编译器的低层,会发现,references往往以指针实现出来,因此passby reference通常意味着真正传递的是指针。

4.      但对于内置类型,以及STL的迭代器和函数对象,pass by value往往比passby reference的效率高些,而且不存在切割问题(slicingproblem).至于其他任何东西(包括STL容器、用户自定义类型等)都请遵守本条款。

条款21:必须返回对象时,别妄想返回其reference

1.      任何函数如果返回一个reference指向某个local对象,都将一败涂地。(如果函数返回指针指向一个local对象,也是一样。)

2.      绝不要返回pointer或reference指向一个localstack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个localstatic对象而有可能同时需要多个这样的对象。

条款22:将成员变量声明为private

1.      某些东西的封装性与“当其内容改变时可能造成的代码破坏量成反比”。

2.      一旦将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切,太多代码需要重写、重新测试、重新编写文档、重新编译。

3.      切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性。

4.      Protected并不比public更具封装性。

条款23:宁以non-member、non-friend替换member函数

条款24:若所有参数皆需类型转换,请为此采用non-member函数

1.      Member函数的反面是non-member函数,不是friend函数。无论何时如果可以避免friend函数就该避免。

条款25:考虑写出一个不抛异常的swap函数

条款26:尽可能延后变量定义式的出现时间

1.“尽可能延后”的真正意义是不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。这样,不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。

条款27:尽量少做转型动作

1.      C++提供了四种新式转型(常常被称为new-style或C++-stylecasts),各有不同的目的:

const_cast<T>(expression):通常被用来将对象的常量性转除(cast away theconstness)。它也是唯一有此能力的C++-style转型操作符。

dynamic_case<T>(expression):主要用来执行“安全向下转型”(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

Reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointerto int转型为一个int。这一类转型在低级代码以外很少见。

static_cast用来强迫隐式转换(implicitconversions),例如将non-const对象转为const对象,或将int转为double等等。它也可以用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但它无法将const转为non-const——这个只有const_cast才能办得到。

条款28:避免返回handles指向对象内部成分

1.      refernces、指针和迭代器统统都是所谓的handles,而返回一个“代表对象内部数据”的handle,随之而来的便是“降低对象封装性”的风险。

条款29:为“异常安全”而努力是值得的

1.      有这么一个类:

class PrettyMenu

{

public:

voidchangeBackground(std::istream& imSrc);

       private:

           Mutexmutex;

                                   Image* bgImage;

                                   Int imageChanges;

};

下面是PrettyMenu的changeBackgroud函数的一个可能实现:

void PrettyMenu:: changeBackgroud(std::istream& imgSrc)

{

                                lock(&mutex);

                                deletebgImage;

                                ++imageChanges;

                                bgImage= new Image(imgSrc);

                                unlock(&mutex);

}

从“异常安全性”的观点来看,这个函数很糟。“异常安全”有两个条件,而这个函数没有满足其中任何一个条件。当异常被抛出时,带有异常安全性的函数会:

*不泄露任何资源。上述代码没有做到这一点,因为一旦”newImage(imgSrc)”导致异常,对unlock的调用就绝不会执行,于是互斥器就永远被lock住了。

*不允许数据破坏。如果”newImage(imgSrc)”抛出异常,bgImage就是指向一个已被删除的对象,imageChanges也已被累加,而其实并没有新的图像被成功安装起来。

2.有个一般化的设计策略,就是copyand swap。原则很简单:为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)。

条款30:透彻了解inlining的里里外外

1.      inline只是对编译器的一个申请,不是强制命令。Inlining在大多数C++程序中是编译期行为。

2.      大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空。

条款31:将文件间的编译依赖关系降至最低

1.      一个简单的设计策略:

*如果使用objectreferences或objectpointers可以完成任务,就不要使用objects.

*如果能够,尽量以class声明式替换class定义式。注意:当你声明一个函数而它用到某个class时,并不需要该class的定义;纵使函数以byvalue方式传递该类型的参数(或返回值)亦然。

2. 可以采用将Pimpl策略和AbstractInterface方法将编译依赖性最小化。

3.程序库文件应该以“完全且仅有声明式”的形式存在。

条款32:确定你的public继承塑模出is-a关系

1.      public inheritance(公共继承)意味”is-a”(是一种)的关系。

2.      如果class D以public形式继承class B,则意味着每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。如果需要一个D对象,B对象无法效劳。

条款33:避免遮掩继承而来的名称

条款34:区分接口继承和实现继承

1.      public继承概念由两部分组成:函数接口继承和函数实现继承。

2.      身为class设计者,有时候希望derived classes只继承成员函数的接口(也就是声明):有时候希望derivedclasses同时继承函数的接口和实现,但又希望能够覆写(override)它们所继承的实现;又有时候希望derivedclasses同时继承函数的接口和实现,并且不允许覆写任何东西。

考虑以下class继承体系:

class Shape

{

public:

virtualvoid draw() const = 0;

virtualvoid error(const std::string& msg);

intobjectID() const;

… …

};

class Rectangle : public Shape { … };

class ellipse : public Shape { … };

*声明一个purevirtual函数的目的是为了让derivedclassed只继承函数接口。

 令人意外的是,我们竟然可以为pure virtual函数提供定义。也就是说可以为Shape::draw供应一份实现代码,C++并不会发出怨言,但调用它的唯一途径是“调用时明确指出其class名称”。

*声明简朴的(非纯)impurevirtual函数的目的,是让derivedclasses继承该函数的接口和缺省实现。

*声明non-virtual函数的目的是为了令derivedclasses继承函数的接口及一份强制性实现。non-virtual函数代表的意义是不变性(invariant)凌驾特异性(specialization),所以它绝不该在derivedclass中被重新定义。

条款35:考虑virtual函数以外的其他选择

1.      藉由non-virtual interface手法实现TemplateMethod模式

也就是“令客户通过publicnon-virtual成员函数间接调用privatevirtual函数”,称为non-virtualinterface(NVI)手法。它是所谓TemplateMethod设计模式的一个独特表现形式。它以publicnon-virtual成员函数包裹较低访问性的virtual函数。

2.      藉由Function Pointers实现Strategy模式

NIV手法对publicvirtual函数而言是一个有趣的替代方案,但从某种设计角度观之,它只比窗饰花样更强一些而已。毕竟我们还是得调用virtual函数。将virtual函数替换为“函数指针成员变量”,这时Strategy设计模式的一种分解表现形式。

3.      藉由tr1::function完成Strategy模式。以tr1::function成员变量替换virtual函数,因而允许使用任何可调用物(callableentity)搭配一个兼容于需求的签名式。这也是strategy设计模式的某种形式。

4.      将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。

条款36:绝不重新定义继承而来的non-virtual函数

条款37:绝不重新定义继承而来的缺省参数值

1.      如条款36所述,绝不重新定义继承而来的non-virtual函数,可以安全地将本条款的讨论局限于“继承一个带有缺省参数值的virtual函数”。Virtual函数系动态绑定(dynamicallybound),而缺省参数值却是静态绑定(staticbound)。

如以下的例子:

//一个用以描述几何形状的class

class Shape

{

Public:

enumShapeColor { Red, Green, Blue };

//所有形状都必须提供一个函数,用来绘出自己

Virtualvoid draw(ShapeColor color = Red) const = 0;

};

Class Rectangle : public Shape

{

public:

//注意,赋予不同的缺省参数值。这真糟糕!

virtualvoid draw(ShapeColor color = Green) const;

};

                   Class Circle : public Shape

  {

  Public:

       virtual void draw(ShapeColor color)const;

          //注:以上这么写则当客户以对象调用此函数,一定要指定参数值。

             因为静态绑定下这个函数并不从其base继承缺省参数值。

             但若以指针(或reference)调用此函数,可以不指定参数值,

                        因为动态绑定下这个函数会从其base继承缺省参数值。

  };

现在考虑这些指针:

Shape* ps;                      //静态类型为Shape*

Shape* pc = new Circle;            //静态类型为Shape*

Shape* pr = new Rectangle;         //静态类型为Shape*

Ps, pc和pr都被声明为pointer-to-Shape类型,所以它们都以它为静态类型。不论它们真正指向什么,它们的静态类型都是Shape*。

对象的所谓动态类型则是指“目前所指对象的类型”。

当执行以下语句:

pr->draw();       //调用Rectangle::draw(Shape::Red)

pr的动态类型是Rectangle*,所以调用的是Rectangle的virtual函数。Rectangle::draw函数的缺省参数值应该是GREEN,但由于pr的静态类型是Shape*,所以此一调用的缺省参数值来自Shapeclass而非Rectangleclass!

条款38:通过复合塑模出has-a或“根据某物实现出”

1.      复合(composition)是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。

条款39:明智而审慎地使用private继承

条款40:明智而审慎地使用多重继承

条款41~48 讨论模板及泛型编程,暂不阅读

条款49~52 讨论重新实现new/delete operator,暂不阅读

条款53:不要轻忽编译器的警告

严肃对待编译器发出的警告信息。

条款54:让自己熟悉包括TR1在内的标准程序库

条款55:让自己熟悉Boost

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值