c++包含与继承ppt课件.ppt
C+包含与继承,复用已有的类,Contents,7.1 包含复用类的实现,包含表示一个类含有一个基本数据元素或对象除了简单类型的数据成员之外,也可以将一个类的对象作为另一个类的成员包含语法有时也被称作“组合”,特指C+中的对象成员语法现象包含用来实现“has-a”关系,对象成员,包含已有类的对象作为成员,可以通过成员对象使用已有类的功能,复用其实现如果将嵌入的对象作为新类的公有成员,那么除了使用新类接口中提供的功能之外,还可以向其中包含的成员对象发送消息程序7.1 public对象成员更常用的方式是将嵌入对象作为新类的私有成员,这时它是新类内部实现的一部分。新类中的方法可以使用成员对象提供的功能,但新类只向外部提供自己的接口,隐藏了包含的成员对象程序7.2 private对象成员,对象成员的初始化,在创建一个包含对象成员的组合对象时,会调用成员类的构造函数初始化对象成员成员对象的初始化使用初始化列表语法当组合对象被撤销时,会调用其析构函数,成员对象的析构函数也会被调用析构函数的调用次序和构造函数相反,class Member int x;public:Member(int iv):x(iv);class withMembersint y;Member m;public: withMembers(int a, int b):m(a),y(b);,对象成员的初始化,成员对象的初始化表如果没有在初始化列表中对成员对象进行显式初始化,编译器会为成员对象调用缺省构造函数,如果成员对象所属的类不存在缺省构造函数,会引起编译错误类中如果包含多个对象成员,在初始化列表中将它们用逗号隔开成员初始化的次序和成员声明的次序相同,并不考虑它们在初始化列表中的排列顺序,class Member1 int x;public:Member1(int iv):x(iv);class Member2 int y;public:Member2(int iv):y(iv);class withMembersMember1 m1;Member2 m2;public: withMembers(int a) : m2(a), m1(a) ;,包含的进一步讨论,包含和类的复用如果希望复用一个类的实现,或使用一个类的功能,使用包含是一种简单而有效的方法。在新类中封装一个已有类的对象,让它作为新类实现的一部分去完成相关的操作,实现新类的功能按值包含包含已有类的对象作为新类的成员按指针(或引用)包含包含成员类对象的指针或引用作为新类的成员,包含的进一步讨论,组合在UML中,将“整体部分”关系称为“组合(composition)”组合关系的特点是成员对象是组合对象的一部分,随着组合对象的创建而创建,随着组合对象的撤销而撤销,成员对象并不作为独立元素对外部展现按值包含用来实现“整体部分”关系,包含的进一步讨论,聚合聚合也描述组成关系,但是比组合更松散聚合关系的特点是成员对象可以独立于聚合对象而存在。当聚合对象被创建或撤销时,其成员对象可以不受影响,而只是它们之间的关系受到影响聚合通常使用按指针(或引用)包含的语法实现,包含的进一步讨论,组合与聚合示例,class MailBody;class Attachmentstring filename;/其他成员;,class Emailstring title;MailBody body;vector attch;public:void edit()void save()void send(string receiverAddr)void addAttch(Attachment* a)attch.push_back(a); ;,包含的进一步讨论,关联关系除了组合与聚合之外,现实中的对象之间还存在着很多更广泛的关系UML中的关联(association)关系关联在C+中用按指针包含的语法实现,class BankAccount/银行账户类long accountNo;double balance;string clientName;public:BankAccount(long aNo, string name, double bal);class Client/客户类string name;string address;BankAccount* acc;/ BankAccount* accounts5; / vector accounts/public:Client(string nm, BankAccount* a):name(nm) acc = a;/一种可能的银行开户操作如下:BankAccount ba(18024, Xavi, 5000);Client Xavi(Xavi, ,7.2 继承复用类的接口,泛化和继承现实中的概念之间还存在另一类常见关系,描述概念的层次或分类如生物系统中的门、纲、目、科、属、种等在UML中用泛化描述这类关系,在C+中则用继承语法实现继承是面向对象的核心特征之一,也是一种复用已有类的机制。在已有类的基础上继承得到新类型,这个新类型自动拥有已有类的特性,并可以修改继承到的特性或者增加自己的新特性,继承的语法,class student /学生string name;int student_id;string department;public:student(string nm, int id, string dp);void print()const;,class grad_student /研究生string name;int student_id;string department;string thesis;public:grad_student(string nm, int id, string dp, string th); void print()const;,继承的语法,研究生类定义中存在的问题在grad_student类中包含了student类的所有成员,两个类的代码有部分重复现实中,研究生也是学生,但是上面的类定义并没有体现出这种关系。应该将grad_student是student的事实更明确地表达出来这样的“is-a”关系用继承实现,class grad_student : public student string thesis;public:grad_student(string nm, int id, string dp, string th);void print()const;,继承的语法,继承的语法形式class 派生类名字 : 访问限定符 基类名字成员声明;如果不指定访问限定符,则默认为private相关术语在C+中,被继承的已有类称为基类,继承得到的新类称为派生类派生类可以再被继承,这样构成的层次结构称为继承层次派生类继承了基类的数据成员和成员函数,只需要对自己不同于基类的行为或自己扩展的行为编写代码,基类成员在派生类中的可见性,基类成员在派生类中的可见性由两个因素决定:成员在基类中的访问限定和继承时使用的访问限定符基类成员的访问限定public成员在任何类和函数中都是可以访问的private成员只有本类或本类的友元可以访问,在其他类和函数中不可访问,在自己的派生类中也同样不可访问protected成员的访问权限介于public和private之间:对它的派生类来说,protected成员和public成员一样是可访问的,但对其他类和函数而言,protected成员就如同private成员一样不可访问,基类成员在派生类中的可见性,ublic公有继承基类的public成员和protected成员分别作为派生类的public成员和protected成员被继承基类的private成员被继承,但在派生类中不可见,基类成员在派生类中的可见性,rivate私有继承基类的public成员和protected成员被派生类作为自己的private成员继承下来基类的private成员被继承,但在派生类中不可见,基类成员在派生类中的可见性,rotected保护继承基类的public成员和protected成员都被派生类作为自己的protected成员继承下来基类的private成员被继承,但在派生类中不可见,基类成员在派生类中的可见性,基类成员在派生类中的可见性,class Basepublic:int m1;protected:int m2;private:int m3;class D1 : public Base / D1中的成员和访问权限如何?;class D2 : private Base / D2中的成员和访问权限如何?;class D3 : protected Base / D3中的成员和访问权限如何?;,基类成员在派生类中的可见性,继承和封装性通过继承基类,派生类就拥有了访问基类protected成员的特权。如果担心基类的封装性会因此被破坏,可以将基类中的所有数据成员都声明为private,而不是protected如果派生类真的需要访问基类的属性,就在基类中为其提供相应的protected访问器函数,class Baseint attr;protected:int getAttr();void setAttr(int);,访问声明,如果要在派生类中对继承的基类成员的可见性进行调整,可以使用访问声明,语法形式为:class Derived : ( public | private | protected ) Basepublic ( | private | protected ):Base:成员名;/;访问声明的作用是在派生类中调整个别基类成员的访问限制,但是不能改变基类private成员的访问限制程序7.3 基类成员访问声明,公有继承和私有继承,公有继承和公有派生类公有派生类继承了基类的接口,能够发送给基类对象的消息派生类对象也可以接收可以将公有派生类看作是基类的子类型,即“is-a(是)”关系。公有派生类的对象也是基类的实例,但反之不成立私有继承和私有派生类私有派生类虽然继承了基类的所有数据和功能,但这些只是作为派生类的部分私有实现,派生类的用户不能访问这些内部功能。基类和私有派生类之间不是类型和子类型的关系,私有派生类的对象也不能被看作是基类的实例,公有继承和私有继承,假定有Shape类的声明如下:class Shape public:void draw() const double area() const void move(int) ;如果想通过继承Shape类来创建一个新类型Line,应该使用public继承还是private继承呢?,公有继承和私有继承,公有继承如果Line要复用Shape类的接口,则使用public继承。公有派生类Line会被看作是一种特殊的Shape,是Shape的子类型。Line类型的对象也是Shape类型的。Shape类中的area()操作显然不适合Line类型,可以通过私有化声明将其隐藏起来程序7.4 公有继承和私有化声明Line对象不能接收Shape对象可以接收的area()消息,Line和Shape不再具有相同的接口,Line严格说来已经不是一种Shape了,公有继承和私有继承,私有继承如果基类中只有个别操作适合新类,而大部分操作对派生类而言是不适合的,派生类不能使用基类的接口,甚至不能将派生类作为基类的子类型,这时可以使用私有继承私有继承时,基类的所有public成员在派生类中都变成了private。要让它们中的某些成员可见,在派生类中进行public访问声明即可。程序7.5 私有继承和公有化声明采用了私有继承,所以Line不具备Shape的接口,也不被看作是Shape的子类型,它们之间不满足“is-a”关系,公有继承和私有继承,私有继承与复用类选择私有继承的一个主要原因是希望隐藏基类的接口私有继承方式使得从基类继承而来的成员隐藏在派生类内部,派生类没有继承基类的接口,派生类对象因而不能被看作是基类的实例,它们之间不满足is-a关系私有继承不再是类接口的复用,而是类实现的复用为了不滥用继承,同时考虑到包含的简单性,在这种情况下,通常会优先使用包含而不是私有继承。protected继承不常用,有时在一些多重继承的应用实例中可以见到protected继承的使用,派生类对象的创建和撤销,派生类对象的结构平面坐标点类三维坐标点类Point3d对象的布局是怎样的?,class Point2dpublic:/对平面坐标点的操作protected:double _x, _y;,class Point3d : public Point2dpublic:/对三维坐标点的操作protected: double _z;,派生类对象的创建和撤销,派生类对象的结构派生类对象由其基类子对象以及派生类自己的非静态数据成员构成在派生类对象中,从基类继承而来的所有成员形成了一个基类子对象,就像是派生类对象的一个无名成员一样在派生类对象的内存中,首先存储基类子对象,接下来存放派生类自己的其他数据成员因此,在派生类中,使用基类的非私有成员就像是使用派生类自己的成员一样,派生类对象的创建和撤销,派生类对象的结构和布局程序7.6 在派生类中使用基类的成员,派生类对象的创建和撤销,派生类对象的创建派生类对象中包含一个基类的子对象,那么在创建派生类对象时,也要初始化这个基类子对象,需要调用基类的构造函数。构造函数调用的次序是先调用基类构造函数,再调用派生类的构造函数。基类构造函数的调用可以是显式的,在派生类构造函数的初始化列表中指出基类构造函数及其实参如果在派生类的构造函数中没有显式调用基类构造函数,编译器会自动调用基类的缺省构造函数来初始化派生类对象中的基类子对象;如果基类不存在缺省构造函数,编译器会报告错误,派生类对象的创建和撤销,包含其他成员对象的派生类对象如果一个派生类中还包含其他类的对象成员,则构造函数的调用次序是:先基类构造函数,再成员类的构造函数,最后调用派生类的构造函数派生类对象的撤销在撤销一个派生类对象时,基类子对象也被撤销。析构函数的调用次序和构造函数的调用次序相反,即,先调用派生类的析构函数,再调用基类的析构函数程序7.7 构造函数和析构函数的调用,派生类对基类的修改,派生类不等同于基类继承使基类的代码进入派生类中,公有派生类便自动拥有了基类的行为和接口如果在派生类中不对这些行为或接口做任何修改,派生类将无法区别于基类,这在实际编程中毫无意义派生类在继承基类的基础上,应该体现与基类的不同在派生类中修改基类的方式有两种:覆盖或隐藏基类的操作:重新定义基类接口中已经存在的操作,从而改变继承到的行为,使得派生类对象在接收到同样的消息时其行为不同于基类对象扩充接口:向派生类的接口中添加新操作,使得派生类对象能够接收更多的消息,覆盖和隐藏,覆盖与同名隐藏如果派生类没有重新定义基类接口中的操作,那么当派生类对象接收到相应的消息时会调用基类操作进行处理程序7.8 Point3d类继承得到Point2d类的成员派生类继承的某些基类操作如果不能满足派生类的需要,就要在派生类中重新定义基类的成员函数在派生类中重新定义基类中的同名成员之后,原来基类中的名字在派生类中被隐藏程序7.9覆盖和名字隐藏,覆盖和隐藏,派生类重新定义基类操作的方式一覆盖在派生类中重定义基类接口中的成员函数,参数表和返回类型保持与基类中一致,这种情况称为覆盖(override)覆盖使派生类与基类的接口保持一致,但是基类的成员函数在派生类中被重定义,因而使派生类对象可以和基类对象接收相同的消息,却表现出不同于基类对象的行为派生类中如果覆盖了基类的同名函数,但是设置了不同的访问限制,也会引起派生类和基类接口的差异,覆盖和隐藏,派生类重新定义基类操作的方式二隐藏在派生类中重定义基类接口中的成员函数,并改变了函数的参数表或返回类型,这种情况称为隐藏(name hiding),在派生类中定义的新版本将自动隐藏基类中的函数版本。隐藏和覆盖的效果不同,因为参数表或返回类型的改变,使得派生类的接口和基类接口已经不再一致,基类对象能够接收的消息,派生类对象将不能处理覆盖实际上是一种隐藏,只不过隐藏的是基类的实现,而保留了基类的接口,覆盖和隐藏,类作用域和名字隐藏C+中每个类都定义了一个类作用域派生类的作用域是包含在其基类作用域之内的作用域的同名隐藏原则:内层作用域中的名字将隐藏外围作用域中相同的名字作用域中名字的查找规则,如果在内层作用域中找不到某个名字,就会继续在其外围作用域中查找;如果在内层作用域中找到了某个名字,就使用这个名字,扩充接口,扩充接口是指为派生类增加更多的数据或操作,class student string name;int student_id;string department;public:student(string nm, int id, string dp);void print()const;class grad_student : public studentstring thesis;public:grad_student(string nm, int id, string dp, string th);void print()const;void research() const;void writepaper() const; ;,扩充接口,is-a和is-like-a如果扩充了派生类的接口,那么派生类对象可以接收的消息就有别于基类对象了能够发送给基类对象的消息仍然可以发送给派生类对象,反之,可以发送给派生类对象的消息未必就能发送给基类对象这时,派生类与基类之间可以被描述为“is-like-a”关系,虽然派生类对象仍然可以替代基类对象,但是这种替代是不纯粹的,替代原则向上类型转换,替代原则继承最重要的特性之一是替代原则在任何需要基类对象(或地址)的地方,都可以由其公有派生类的对象(或地址)代替。替代原则有时也被称为赋值兼容规则向上类型转换在C+语言中,替代原则直接由编译器支持,也称为向上类型转换公有派生类就是基类的子类型,公有派生类的对象可以自动转换为基类类型,基类的指针和引用可以指向派生类的对象,class Base ;class Derived : public Base;int main()/派生类对象代替基类对象Base b;Derived d;b = d;/派生类左值代替基类左值Base,替代原则向上类型转换,对象、引用和指针的向上类型转换程序7.11对象向上类型转换和对象切片派生类对象在向基类对象转换时,会发生“对象切片”现象派生类对象被“切片”,直到剩下适合的基类子对象对象切片实际上是在将它拷贝到一个新对象时,去掉原来对象的一部分由于会损失派生类对象的部分内容,因此,不常使用对象的向上类型转换,替代原则向上类型转换,替代原则向上类型转换,指针和引用向上类型转换使用指针向上类型转换只是简单地改变地址的类型,是用不同的方式解读同一段内存空间中的内容,并不会真正切除派生类对象的多余部分引用向上类型转换与指针相似指针和引用在进行转换时虽然不会发生对象切片的现象,但同样会损失对象的类型信息。,替代原则向上类型转换,赋值时p3没有切片;赋值后pt2中是p3的地址,或者说pt2指向p3。通过pt2间接访问p3时却只能将其作为pt2自己的类型(Point2D)的对象解释。,替代原则向上类型转换,向上类型转换总是安全的在逻辑上,公有派生类继承基类的公共接口,能够发送给基类对象的消息也能够发送给派生类对象派生类是特殊的基类类型,派生类对象(或地址)可以作为基类的实例(或地址),替代基类对象(或地址)使用在物理上,基类的成员被派生类继承,在派生类对象中封装着一个无名的基类子对象,派生类对象的存储空间中从首地址开始存放这个基类子对象进行向上类型转换时,派生类对象能够提供足够的基类信息,通过对象切片得到基类对象使用基类指针(或引用)指向派生类对象时也不会破坏指针(或引用)的指向规则,替代原则向上类型转换,遗留问题对象向上类型转换会发生对象切片现象指针和引用在进行转换时同样会损失对象的类型信息通过基类的指针或引用调用成员函数时,即使它们指向的派生类对象,调用的都是基类的成员函数编译器不知道它们实际指向的是派生类类型的对象如果希望根据指针实际指向对象的类型来实施成员函数的调用,应该如何处理?动态绑定和虚函数,不能自动继承的成员,并不是所有的基类成员都能被派生类继承,下列成员函数是不能继承的 构造函数、析构函数、operator=()如果在派生类中没有定义这些函数,编译器在必要时会自动生成派生类的缺省构造函数、拷贝构造函数、析构函数和operator=()在编译器自动生成的构造函数中会调用相应的基类构造函数来完成基类子对象的初始化编译器自动生成的operator=()只能用于同类型对象之间的赋值,其行为是按成员赋值,如果要对不同类型的对象赋值,需要自己定义operator=(),7.3 多重继承,单继承和多重继承如果一个派生类只有唯一的基类,称为单继承一个派生类直接继承多个基类,称为多重继承多重继承的语法class 派生类名 : 访问限定符 基类1, 访问限定符 基类2 ,访问限定符 基类3, 成员声明 ;派生类直接继承的多个基类必须是不同的类型,每个基类都有自己的访问限定符,缺省为private。,7.3 多重继承,派生类对象的创建和撤销有多个基类的派生类对象中会包含各个基类的子对象在创建派生类的对象时,依据基类的声明次序来调用各个基类的构造函数初始化这些子对象构造函数的调用次序与初始化列表中的排列次序无关析构函数的调用次序和构造函数的调用次序相反,class B1;class B2;class D1 : public B1, public B2public:D1():B2(),B1();D1 obj;/?,多重继承,类层次中的构造函数调用派生类对象的创建会引起其直接基类的构造函数调用在继承层次中,底层派生类对象的创建会引起其父类及祖先类的一连串构造函数调用根据基类的声明次序,从左向右,处于继承层次最上方(根)的基类构造函数最先被调用,class A;class B;class C : public A ;class D : public B ;class E : public C, public B;class F : public D, public C;,多重继承的二义性,如果两个基类中有同名的成员,在派生类中使用这个成员时会产生二义性这样的二义性可以通过显式指定类名来消除,多重继承的二义性,class A ;class B : public A ;class C : public A ;class D : public B, public C ;,同一个类间接地被继承多次引起的二义性在同一个派生类对象中,该基类构造函数多次调用,基类子对象出现多次,虚基类,可以将基类声明为虚基类(virtual base class),这种继承方式也被称为虚继承。虚基类在继承层次中无论出现多少次,最终的派生类对象中也只会存在一个共享的基类子对象可以解决因同一基类被多次间接继承引起的二义性问题声明虚基类的语法:class 派生类名 : virtual 访问限定符 基类名或者:class 派生类名 : 访问限定符 virtual 基类名,虚基类,class A ;class B : virtual public A ;class C : virtual public A ;class D : public B, public C ;,通常将可能被一个派生类重复继承的基类声明为虚基类将A声明为虚基类,而不是B或CA类的子对象在D中只出现一次,虚基类,带虚基类的类层次中构造函数的调用虚基类的构造函数是由最终产生对象的那个派生类的构造函数来调用的,虚基类的构造函数只调用一次无论虚基类出现在继承层次中的哪个位置上,它们都是在非虚基类之前被构造有虚基类的派生类的构造函数调用次序编译器按照直接基类的声明次序,检查虚基类的出现情况,对每棵继承子树按照深度优先的顺序检查并调用虚基类的构造函数,同一虚基类的多次出现只调用一次构造函数虚基类的构造函数调用之后,再按照声明的顺序调用非虚基类的构造函数,虚基类,class A ;class B ;class C: public B, public virtual A ;class D: public virtual A ;class E: public C, public virtual D ;,class A ;class B : public A ;class C ;class D ;class E: public virtual D ;class F: public B, public E, public virtual C ;,E类对象的构造函数的调用次序是:,F类对象的构造函数的调用次序是:,D,C,A,B,E,F,A,D,B,C,E,多重继承的使用,应该慎用多重继承,在决定使用多重继承之前,先仔细考虑是否存在其他的替代方案多重继承会增加复杂性多重继承能够被认可的用途主要是定义混入类,即一些能够为对象增加一组特性的简单类混入类(mixin)通常是没有实现的抽象类,之所以称为混入类,是因为它们可以把一些特性“混入”到派生类里接口的多重继承有一定价值,但应避免实现的多重继承一般建议的使用方式是对一个基类采用public继承,继承其接口,对其他的基类使用protected或private继承,继承其实现。这样能减少可能的混乱。,7.4 包含与继承的选择,面向对象的代码复用创建一个类并进行测试之后,这个类就成为可复用的代码单元复用一个类最简单的方法是以该类为模板创建对象包含将一个类的对象嵌入一个新类中,使之成为成员对象,以组成更复杂的组合对象包含是一种简单灵活的代码复用方式,是面向对象编程中的主力技术继承机制也提供了一种有效的代码复用方法继承需要更多的技巧,而且更容易出错,包含vs继承,包含复用类的实现将已有类型的对象作为新类型的私有成员,这使得被嵌入的成员成为了新类型的内部实现,新类型可以不受其成员的约束,向外提供完全不同的接口。即使其内部成员或实现方式发生改变,也不会影响外部客户代码灵活,简单有效,包含vs继承,继承复用类的接口派生类与基类之间具有接口相似性,形成 “is-a”关系派生类可以看作是基类的特殊子类型,派生类对象可以替代基类对象是否使用继承的一个重要依据便是考察类之间是否存在这种关系,是否需要由基类提供公共接口,是否需要向上类型转换,包含vs继承,何时可以使用继承,何时又该使用包含?如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象。如果多个类共享行为而非数据,应该让它们从共同的基类继承而来,并在基类里定义共用的操作。如果多个类既共享数据也共享行为,应该让它们从一个共同的基类继承而来,并在基类里定义共用的数据和操作。如果想由基类控制接口,使用继承;如果想自己控制接口,使用包含。,包含的应用,一个问题假定已有整型链表类slist,其部分声明如右如果要求在slist类的基础上,设计并实现一个整型队列类IntQueue。应该选择包含还是继承呢?,class slist public:slist();slist();void insert_front(int x);void insert_back(int x); int del_front(); int del_back(); int front(); int back(); bool empty(); /;,包含的应用,使用继承,可以得到如下的IntQueue类,class IntQueue : public slistpublic:IntQueue():slist() IntQueue() void in_queue(int ele) insert_back(ele); int del_queue()return del_front();bool isEmpty()return empty();int head()return front();,包含的应用,使用继承存在的问题继承使派生类从基类那里获得基类接口的所有操作,能够向基类对象发送的消息也可以向派生类对象发送,而这些从基类继承而来的操作将无法保证队列操作的正确性继承并保留的基类接口不适合这个队列类使用继承,就意味着队列是一种链表,需要链表的地方可以用队列来代替,包含的应用,使用包含更合理链表是队列的一种实现方式,这两个类有着完全不同的接口队列复用的应该是slist的实现,而不是slist的接口将slist对象作为队列的私有成员,class IntQueuepublic:IntQueue()IntQueue()void in_queue(int ele)list.insert_back(ele);int del_queue()return list.del_front();bool isEmpty()return list.empty();int head()return list.front();private:slist list;,包含的应用,与继承相比,包含体现了更大的灵活性对已有类的实现依赖小如果对基类的实现代码做了修改并重新编译,那么它所有派生类的代码都需要重新编译包含对象成员的类对其成员对象的依赖性要小一些,只依赖成员类的接口,只要接口保持不变,组合类的代码就无需修改已有类的接口改变不影响使用新类的客户代码如果基类接口改变,那么派生类的接口也会随之改变,使用派生类的客户代码自然会受到影响。包含在类中的对象往往作为私有成员,是隐藏起来的实现的一部分,并没有展现自己的接口,因此,成员类的变化不会影响组合类的接口,其客户代码根本不会知道这样的改变,自然不受影响,包含的应用,一个设计问题有一个要使用类Circle的客户程序如下如果要求你设计并实现一个Circle类,满足这段客户程序对Circle类接口和功能的需求,你会如何完成这项工作?,void client()Circle c;c.draw(); /绘制圆形double a = c.area();/计算c的面积double p = c.perimeter(); /计算c的周长c.scale(2);/将c放大2倍,包含的应用,如果你找到了一个XCircle类,它提供了这个客户程序需要的所有功能,只是接口不同,/xcircle.hclass XCircle public:XCircle(); /构造函数void Xdraw();/绘制圆形double calc_area();/计算面积double calc_perimeter();/计算周长void zoom(double factor); /按比例缩放/,包含的应用,复用Xcircle类的方法一如果可以获得源码,修改其源代码如果不能获得源代码,或者担心因修改代码引入错误,该怎么办呢?方法二Wrapper(Adapter)设计模式应用包含,重新包装一个类对象,使它用于特定的环境中重新包装XCircle来实现客户程序需要的Circle类这种复用方法可以推广到更大的范围:构件、子系统,包含的应用,#include xcircle.hclass Circle public:Circle() : xc() void draw() xc.Xdraw();double area() return xc.calc_area(); double perimeter() return xc.calc_perimeter(); void scale(double factor) xc.zoom(factor);private:XCircle xc;,继承的应用,继承的目的是通过“定义能为多个派生类提供共有元素的基类”的方式编写更精简的代码。共有元素可以是操作接口、内部实现、数据成员或数据类型等。继承能够把这些共有元素集中在一个基类中,从而避免在多处出现重复的代码和数据。避免“继承滥用”问题程序员应该慎用继承关系,不要只是为了复用类代码而使用继承,而应该在确定类之间确实存在“is-a”关系、具有公共接口并且需要向上类型转换时再考虑使用继承。如果只是为了复用一个类的实现,使用包含是更好的选择。,继承的应用,一个问题某公司的管理系统中已有employee类用于表示公司员工,公司中的员工类型包括经理、程序员和兼职人员,他们的工资计算方式互不相同。如果公司财务部需要一个通用函数payroll()来发放员工的工资,应该如何设计?是否使用继承?经理、程序员和兼职人员都是员工,与employee之间存在is-a关系payroll()函数被要求设计为通用函数,因此需要面向employee来实现该函数,那么就存在由派生类向基类进行类型转换的需求,继承的应用,通过继承employee来创建各员工类,并且面向基类employee实现payroll函数,class employee/员工共有的信息public: void salary() ;class manager : public employeepublic: void salary()/*经理工资的计算和发放*/;class programmer : public employeepublic: void salary()/*程序员工资的计算和发放*/;class parttime : public employeepublic: void salary()/*兼职人员工资的计算和发放*/;/payroll函数void payroll(employee ,继承的应用,ayroll()函数的通用性payroll()函数的形参类型是基类引用,无论用哪种员工的实例来调用这个函数,都会发生实参到形参的向上类型转换,manager Harry; programmer Ron;parttime Hermione;payroll(Harry);payroll(Ron);payroll(Hermione);,向上类型转换的遗留问题参数经过向上类型转换之后,编译器只知道参数re是employee类型的,并不知道它实际引用对象的具体类型,从而payroll函数使用的总是employee版本的salary(),而无法调用到各个派生类中重新定义的版本,继承的应用,当决定使用继承时,必须要做出如下决策对每个数据成员而言,它应该对派生类可见吗?通过访问限定可以控制成员在派生类中的可见性对于每个成员函数而言,它应该对派生类可见吗?应该有默认实现吗?默认的实现可以被覆盖吗?覆盖与隐藏和下一章将要讨论的虚函数与抽象类等机制可以为此提供技术上的支持,继承的应用,关于继承层次要注意的问题不要创建任何并非绝对必要的继承结构,例如,只有一个派生类的基类。在继承层次中,将公共接口、数据及操作放到尽可能高的位置,以便派生类使用避免让继承层次过深有的学者认为“72”理论适用于此处,但实际应用的经验表明,超过3层的继承深度就有麻烦了。过深的继承层次会增加复杂度,会导致错误率的显著增长。,本章小结,包含与继承都是复用已有类的机制。包含是对类实现的复用,而继承是对类接口的复用。将已有类的实例作为新类的对象成员称为按值包含,通常用来实现面向对象设计中的组合关系。将已有类的指针作为新类的成员被称为按指针包含,通常用来实现面向对象设计中的聚合和关联等较松散的关系。创建包含对象成员的组合对象时会引起成员类构造函数的调用。撤销一个组合对象时,其成员类的析构函数也会被调用。派生类将继承基类的所有成员,成员在派生类中的可见性由它在基类中的访问权限和继承时使用的访问限定符决定,本章小结,公有派生类继承了基类的接口,是基类的子类型。在任何需要基类对象(或地址)的地方,都可以由其公有派生类的对象(或地址)代替派生类可以增加新的成员,扩充基类的接口。派生类可以重定义基类中已有的成员,修改基类的行为。派生类中重定义的名字将隐藏基类中相同的名字从多个基类共同派生出新的类型被称为多重继承。虚基类可以避免因同一