欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    类的继承与派生谭浩强C.ppt

    • 资源ID:3825635       资源大小:746.02KB        全文页数:67页
    • 资源格式: PPT        下载积分:12金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要12金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    类的继承与派生谭浩强C.ppt

    类的继承与派生,前面我们主要讨论了面向对象程序设计的第一个重要机制-数据的封装与隐藏特性。而面向对象程序设计中另一个重要机制就是代码的可重用性,代码可重用性的特征实现的主要机制之一就是类的继承。类的继承机制自动为一个类提供来自于另一个类的操作和数据结构,这使得程序员只需在新类中定义已有类中没有的成分来建立新类。更为实际的意义就在于,类的继承机制给程序员提供了无限重复利用程序资源的一种有效途径。通过C+语言中的继承机制,可以扩充和完善旧的程序设计以适应新的需求,这样不仅可以节省程序开发的时间和资源,并且为未来程序设计增添了新的资源。,1基类和派生类,2单继承,3多继承,4虚基类,综上所述,理解继承是理解面向对象程序设计所有方面的关键。所以,本章是整个面向对象程序设计中的重点内容。通过本章的学习,主要理解与掌握基类和派生类、单继承、多继承及虚基类的基本概念及其它们在面向对象程序设计中的基本应用。,基类和派生类,继承的机制提供了利用已有的数据类型来定义新的数据类型的途径。,所定义新的数据类型不仅拥有新定义的成员(数据成员与成员函数),而且还同时拥有已存在的成员。,我们将这种利用已知的类来定义新类的机制称之为类的继承。,我们称已存在的用来定义新类的类为基类,又称为父类。由已存在的类派生出的新类称之为派生类,又称为子类。,这样,派生类继承了它父类的属性和操作。同时,在子类中也可声明新的属性和新的操作,剔除了那些不适合于其用途的继承下来的操作。这种机制,使得程序员可重用父类的代码,将注意力集中在为子类编写新的代码。,继承是我们理解事物,解决问题的方法。继承可帮助我们描述事物的层次关系,帮助我们精确地描述事物,帮助我们理解事物的本质。在解决某一问题时,只要弄清事物所处的层次结构,也就找到了对应的解决方法。,在C+语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承。从多个基类派生的继承称为多继承。单继承形成了类的层次,像一棵倒挂的树。多继承形成了一个有向无环图。如图所示。,基类和派生类,基类和派生类,一派生类的定义,单继承的定义格式如下:,class : ;,其中,是新定义的一个类的名字,它是从中派生的,并且按指定的派生的。,常使用下列三种关键字给予描述:,public:表示公有继承。,private:表示私有继承。,protected:表示保护继承。,这三种继承的意义,在后讨论。,基类和派生类,多继承的定义格式如下:,class : , ,. ;,二派生类的三种继承方式,公有继承(public)、私有继承(private)和保护继承(protected)是常用的三种继承方式。, 公有继承(public),公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。, 私有继承(private),私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所继承与访问。,基类和派生类, 保护继承(protected),保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,而这种继承关系还可向其子类进行传递。基类的私有成员仍然是私有的。,为了进一步理解三种不同继承方式在其成员的可见性方面的区别,下面从三种不同角度进行讨论。, 基类成员对基类对象的可见性:在基类外部,通过基类对象对基类成员的可访问性。, 基类成员对派生类成员函数的可见性:派生类成员对继承下来基类成员的可访问性。, 基类成员对派生类对象的可见性:在派生类外部,通过派生类对象对基类成员的可访问性。,对于公有继承方式:, 基类成员对基类对象的可见性:,公有成员可见,其它不可见。这里保护成员等同于私有成员。,基类和派生类, 基类成员对派生类成员函数的可见性:,公有成员与保护成员可见,而私有成员不可见。这里保护成员等同于公有成员。, 基类成员对派生类对象的可见性:,公有成员可见,其它成员不可见。,结论:在公有继承时,派生类的对象可以访问基类中的公有成员。派生类的成员函数可以访问基类中的公有成员和保护成员。,对于私有继承方式:, 基类成员对基类对象的可见性:,公有成员可见,其它成员不可见。, 基类成员对派生类成员函数的可见性:,公有成员和保护成员可见,而私有成员是不可见的。, 基类成员对派生类对象的可见性,所有成员都是不可见的。,结论:在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。,基类和派生类,对于保护继承方式与私有继承方式的情况基本相同。两者的区别就在于,基类的公有成员与保护成员在私有继承的方式下,在派生类中不允许再往下继承,而保护继承方式则允许继续往下传递。,可将可见性理解为可访问性。关于可访问性的另外一种规则中,称派生类对象对基类的访问为水平访问,称派生类的派生类对基类的访问为垂直访问。则上述讨论可总结如下:,公有继承时,水平访问和垂直访问对基类中的公有成员不受限制。,私有继承时,水平访问和垂直访问对基类中的公有成员也不能访问。,保护继承时,对于垂直访问同于公有继承,对于水平访问同于私有继承。,结论:对于基类中的私有成员,只能被基类中的成员函数和友元函数所访问,不能被其它的函数访问。,三基类与派生类的关系,任何一个类都可以派生出一个新类,派生类也可作为基类再派生出新类。因此,基类与派生类的关系是相对而言的。,基类和派生类,这就意味着:一个基类可以是另一个基类的派生类,这样便形成了复杂的继承结构,出现了类的层次。一个基类派生出一个派生类,它又可作为另一个派生类的基类,则原来的基类为该派生类的间接基类。其关系如右图所示。,其中,类A是类C的间接基类,而类B是类A的直接派生类。,基类与派生类之间的关系,可从以下几个方面进行理解:, 派生类是基类的具体化。,类的层次通常反映了客观世界中某种真实的模型。例如,定义输出设备为基类,而显示器、打印机等是派生类,它们的关系如下图所示。,基类和派生类,在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象变为某种有用的类型。, 派生类是基类定义的延续,先定义一个抽象类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作,虚函数就属于此类情况。这时,派生类是抽象基类的实现。因此,将派生类看成基类定义的延续,是常用的方法之一。, 派生类是基类的组合,在多继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。,派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以可以称类是“可复用的软件构件”。,单继承,在单继承中,每个类中可以有多个派生类,但是每个派生类只能有一个基类,从而形成树形结构,如下图所示。,单继承,一成员访问权限的控制,例5.1 分析下列程序中的访问权限,并回答所提出的问题。,#include class A public: void f1(); protected: int j1; private: int i1; ;,class B:public A public: void f2(); protected: int j2; private: int i2; ;,class C:public B public: void f3(); ;,问题:, 派生类B中成员函数f2()能否访问基类A中的成员:f1()、i1和j1?,答案:可访问f1()和j1,不可访问i1 。,单继承, 派生类B的对象b1能否访问基类A中的成员:f1()、i1和j1?,答案:可访问f1(),而不可访问i1和j1 。, 派生类C中成员函数f3()能否访问直接基类B中的成员:f2()和j2?能否访问间接基类A中的成员f1()、j1和i1?,答案:f3()可以访问f2()与j2及间接基类中的f1()和j1,不可以访问i2和i1 。, 派生类C的对象c1能否访问直接基类B中的成员:f2()、i2和j2?能否访问间接基类A中的成员:f1()、j1和i1?,答案:可以访问f2()和f1(),其它的都不可访问 。,由此分析,可得出结论:在公有继承时,派生类的成员函数可访问基类中的公有成员和保护成员。派生类的对象仅可访问基类中的公有成员。,考虑:将程序中的两处继承方式的public改为private,上述的访问权限又如何变化?,单继承,例5.2 分析下列程序,指出错误之处,并给出纠正办法。,#include class A public: void f(int i) cout<<i<<endl; void g() cout<<gn; ;,class B:A public: void h() cout<<hn; A:f;/此语句的含意为:将类A中的成员名为f的成员作为B类公有成员. ;,单继承,void main() B d1; d1.f(6); d1.g(); d1.h(); ,g()在类A中为公有成员,继承到B类中为私有成员,故B类对象对其无访问权限。,说明:使用class关键字定义类时,缺省的继承方式是private。,例5.3 分析下列程序,指出错误之处,并给出纠正办法。,#include #include class A public: A(const char *nm) strcpy(name,nm); private: char name80; ;,单继承,class B:public A public: B(const char *nm) :A(nm) void PrintName() const; ;,继承类的构造函数必须包含对基类对象的构造.,void B:PrintName() const cout<<name: <<name<<endl; ,name为A类的私有成员,因此不允许被其它类的成员访问.,void main() B b1(Wang Li); b1.PrintName(); ,单继承,二构造函数与析构函数,派生类的构造函数与析构函数如何定义及派生类对象的构造与析构是继承讨论中的主要问题,因此本节所讨论的课题是继承问题中的重点内容。, 构造函数,派生类对象的数据结构(属性)是由基类中说明的数据成员和派生类中说明的数据成员共同组成。因此,派生类对象中包含有由基类中说明的数据成员和操作所构成的封装体,称之为基类子对象,它必须由基类中的构造函数来完成对其的构造及初化的操作。,构造函数不能被继承,所以,派生类的构造函数必须通过调用基类的构造函数来初始化基类的子对象。因此,在定义派生类的构造函数时除了对自己的数据成员进行初始外,还必须负责调用基类构造函数使基类的数据成员得以初始化。如果派生类中还有子对象时,还应包含对子对象初始化的构造函数。,派生类对象的构造,基类子对象构造与初始化,自身声明数据成员的初始化,基类构造函数,派生类构造函数,单继承,派生类构造函数的一般格式如下:,():(),(),. ,派生类构造函数的调用顺序如下:,基类的构造函数,调用顺序按照被继承时声明的顺序。,子对象类的构造函数(如果有),调用顺序按照定义顺序。,派生类构造函数 。,例5.4 分析下列程序的输出结果:,#include,单继承,class A public: A() a=0; cout<<A类默认构造函数被调用!n; A(int i) a=i; cout<<A类构造函数被调用!n; A() cout<<A类对象被析构!n; void Print() const cout<<a<<,; int Geta() return a; private: int a; ;,单继承,class B:public A public: B() b=0; cout<<B类默认构造函数被调用!n; B(int,int ,int); B() cout<<B类对象被析构!n; void Print() A:Print(); cout<<b<<,<<aa.Geta()<<endl; private: int b; A aa;/A类的子对象 ;,单继承,B:B(int i,int j,int k):A(i),aa(j) b=k; cout<<B类构造函数被调用!n; ,void main() B bb2; bb0=B(1,2,5); bb1=B(3,4,7); for(int i=0;i<2;i+) bbi.Print(); ,单继承,单继承,说明:,先创建两个对象元素的对象数组。调用两次类B的默认构造函数,每调用类B的默认构造函数,先调用两次类A的默认构造函数来创造基类子对象与aa子对象,于是出现前6行的输出结果。,在程序的两个赋值语句中,系统调用B类构造函数建立匿名对象,赋值后,再调用析构函数析构对象,由于对象析构顺序与构造顺序相反,所以出现两个调用派生类B的构造函数与析构函数的12行信息。,输出两个类B对象的数据成员值,占有两行信息。,最后,程序结束时,要析构数组对象,因此,输出最后6行信息。,析构函数,我们知道,当对象的生命周期结束时,系统会自动调用析构函数完成对象的析构,对于派生类的对象也是如此。也就是说,当派生类对象的生命周期结束时,派生类的析构函数被执行。由于析构函数同样没有继承性,因此,在执行派生类的析构函数时,基类的析构函数也将被调用。在这里,我们关心的是执行顺序的问题。由于析构顺序与构造顺序相反,所以在执行析构函数时,先执行派生类的析构函数,再执行基类的析构函数。,单继承,例5.5 分析下列程序的运行结果。,#include class M public: M(int i=0,int j=0) m1=i; m2=j; void print() cout<<m1<<,<<m2<<,; M() cout<<M类析构函数被调用!n; private: int m1,m2; ;,class N:public M public: N() n=0; N(int i,int j,int k):M(i,j),n(k) void print() M:print(); cout<<n<<endl; N() cout<<N类析构函数被调用!n; private: int n; ;,单继承,void main() N n1(5,6,7),n2(-2,-3,-4); n1.print(); n2.print(); ,单继承,派生类构造函数使用中应注意的问题,派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有默认的构造函数或根本没有定义构造函数。如果是这样的话,则派生类不必负责调用基类的构造函数。,例5.6 分析下列程序的输出结果。,#include class A public: A(int i=0) a=i; void print() cout<<a<<,; private: int a; ;,单继承,void main() B d1,d2(5),d3(4,5,6); d1.print(); d2.print(); d3.print(); ,class B:public A public: B() b1=b2=0; B(int i) b1=i; b2=0; B(int i,int j,int k):A(i),b1(j),b2(k) void print() A:print(); cout<<b1<<,<<b2<<endl; private: int b1,b2; ;,单继承,当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。如果派生类构造函数的作用仅仅起到这样的作用,则其函数体可以为空。,例如有以下定义: class B public: B(int i,int j) b1=i; b2=j; /. private: int b1,b2; ;,单继承,class D:public B public: D(int i,int j,int k,int l,int m):B(i,j),bb(k,l) d1=m; /. private: int d1; B bb; ;,分析:派生类D的构造函数中有5个参数,前两个参数传递给基类B的构造函数,接着的两个参数用于构造子对象同样传递给B类的构造函数,最后一个参数用于派生类D数据成员的初始化。,单继承,派生类生成过程, 吸收基类成员,在类的继承中,首先是派生类先将基类的成员全盘接收。这样,派生类实际上就包含了基类中除构造函数、析构函数之外的所有成员。问题是,尽管很多基类的成员,特别是非直接基类的成员,在派生类中很可能根本就不起作用,却也被继承下来,在生成对象时也要占用内存空间,造成资源浪费。, 改造基类成员,对基类成员的改造包括两个方面,一是基类成员的访问控制问题,主要依靠派生类定义时的继承方式来控制。第二是对基类数据成员或成员函数的覆盖,就是在派生类中定义一个和基类数据成员或成员函数同名的成员,由于作用域不同,于是发生同名覆盖,基类中的成员就被替换成派生类中的同名成员。, 添加新的成员,派生类新成员的加入是继承与派生机制的核心,是保证派生类在功能上有所发展的关键。程序员可以根据实际情况的需要,给派生类添加适当的数据和函数成员,来实现必要的新增功能。在这里,特别强调的是,在派生过程中,基类的构造函数和析构函数是不能被继承下来的。,单继承,三子类型化和类型适应,子类型化,子类型的概念涉及到行为共享,它与继承有着密切关系。,概念:有一个特定的类型S,如派生类,当且仅当它至少提供了类型T的行为,如基类的行为,则称类型S是类型T的子类型。子类型是类型之间的一般和特殊的关系。,在继承中,公有继承可以实现子类型。,例如有如下的定义:,例5.7 #include class A public: void Print() cout<<A:print()被调用.n; ;,单继承,class B:public A public: void f() ;,类B以公有继承方式继承了类A,因此可以说类B是类A的一个子类型。类A还可以有其它的子类型。类B是类A的子类型,意味着类B具备类A中的操作(行为),或者说类A中的操作可以被用于类B的对象。,分析下列程序: void f1( A ,单继承,运行结果:,从运行结果可以看出,类B的对象可作为A类的引用型对象的目标,因此,从类型上讲是相容的。同时。对类A的对象操作的函数,也可以对类A的子类型(类B)的对象进行操作。,值得说明的是,子类型是不可逆的。类B是类A的子类型,但不能认为类A是类B的子类型,换句讲,子类型的关系是不对称的。,这样可以得出一个结论:公有继承可以实现子类型化。 公有继承所导致派生类与基类的关系,实质是“is a”关系,也就是说:派生类是基类的子类型,且是不可逆的;而私有继承与保护继承所导致派生类与基类之间的关系,可认为是“has a”关系。,单继承,类型适应,类型适应是指两种类型之间的另一种关系。体现最为具体的是类继承之间的一种类型适应关系。在上例中,我们可以讲,类B是适应类A的,这就意味着类B的对象能够用于A类型的对象所能使用的场合。,这样,派生类对象可以用于基类对象所能使用的场合,我们说派生类适应于基类。,同样的道理,派生类对象的指针和引用也适应于基类对象的指针和引用。,子类型化与类型适应是一致的。若一类型是另一类型的子类型,则该类型必将遵守类型适应的原则,也就是说该类型必将适应于另一类型。,子类型化的重要性就在于减少程序员编写程序代码的负担,这是因为一个函数可以用于某类的对象,则它也可用于该类型的各个子类型的对象。,例5.8 分析下列程序的输出结果。 #include,单继承,class A public: A(int i=0) a=i; void print() cout<<a<<endl; int geta() return a; private: int a; ;,class B:public A public: B() b=0; B(int i,int j):A(i),b(j) void print() A:print(); cout<<b<<endl; private: int b; ;,单继承,void fun(A ,void main() B bb(9,6); A aa(5); aa=bb; aa.print(); A *pa=new A(8); B *pb=new B(1,2); pa=pb; pa-print(); fun(bb); ,单继承,运行结果:,此结果再一次说明了子类型化与类型适应的问题。,赋值兼容原则,类型的适应性,在有的资料或教课书上也称为赋值兼容原则。我们通过赋值兼容原则的这个角度,对上述内容再作一小结。,所谓赋值兼容,与类型适应性是同一个概念。亦即,指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。,单继承,通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同。这样公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。,赋值兼容具体的原则如下:,派生类的对象可以赋值给基类对象。,派生类的对象可以初始化基类的引用。,派生类对象的地址可以赋给基类类型的指针。,在完成上述兼容的赋值之后,派生类对象就可以作为基类的对象使用,但值得关注的问题是,只能使用从基类继承的成员。,多继承,一多继承的概念,多继承可以看作是单继承的扩展。所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可看作是一个单继承。,多继承,多继承下派生类的定义格式如下:,class : , ,. ;,其中,继承方式的含义与单继承继承方式的含义相同。,二多继承派生类的构造函数,在多继承的情况下,派生类的构造函数如下:,():(),(),.(). 派生类新定义成员的初始化语句; ,其中,中各个参数包含了其后的各个分参数表。,多继承,多继承下派生类的构造函数与单继承下派生类的构造函数类似,它必须负责派生类所有基类构造函数的调用。同时,派生类的参数必须包含完成所有基类初始化所需的参数。,派生类构造函数执行顺序是先执行所有基类的构造函数,再执行派生类本身构造函数。处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类的顺序,与派生类构造函数中所定义的成员初始化列表的顺序无关。,例5.9 分析下列程序的行动结果。 #include class B1 public: B1(int i) b1=i; cout<<B1类对象被构造!其值为:<<b1<<endl; void print() cout<<b1<<endl; private: int b1; ;,多继承,class B2 public: B2(int i) b2=i; cout<<B2类对象被构造!其值为:<<b2<<endl; void print() cout<<b2<<endl; private: int b2; ;,class B3 public: B3(int i) b3=i; cout<<B3类对象被构造!其值为:<<b3<<endl; void print() cout<<b3<<endl; int getb3() return b3; private: int b3; ;,多继承,class A:public B2,public B1 public: A(int i,int j,int k,int m) :B1(i),B2(j),bb(k) a=m; cout<<A类对象被构造!其值为:<<a<<endl; void print() B1:print(); B2:print(); cout<<a<<,<<bb.getb3()<<endl; private: int a; B3 bb; ;,void main() A aa(1,2,3,4); aa.print(); ,多继承,运行结果:,多继承,三关于二义性的讨论,在派生类中对基类成员的访问应该是惟一的。但在多继承的情况下,可能会造成对基类中某个成员的访问出现不惟一的情况,称之为对基类成员访问的二义性问题。,解决对基类成员访问二义性问题的一个途径就是用作用域运算符对成员进行惟一标识。,我们首先回顾一下,在不同作用域声明的标识符的可见性原则:如果存在两个或多个具有包含关系的作用域,外层声明的标识符如果在内层没有声明同名标识符,那么它在内层仍可见;如果内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符覆盖了外层同名标识符,这个原则称为同名覆盖原则。,在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用范围不同,是相互包含的两个层,派生类在内层。这时,如果派生类声明了一个和某个基类成员同名的新成员(如果是成员函数,则参数的特征标也要相同,若参数特征标不同属于函数的重载),派生的新成员就覆盖了外层同名成员,直接使用成员名只能访问到派生类的成员。如果加入作用域运算符,使用基类名来进行限定,就可以访问到基类的同名成员。,多继承,对于多继承情况,我们首先考虑各个基类之间没有任何继承关系,同时也没有共同基类的情况。最典型的情况就是所有基类都没有上级基类。如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将覆盖所有基类的同名成员。这时使用“对象名.成员名”方式可惟一标识和访问派生类新增成员,基类的同名成员也可使用作用域运算符访问。但是,如果派生类没有声明同名成员,“对象名.成员名”方式就无法惟一标识成员,这时,从不同基类继承过来的成员具有相同的名称,同时具有相同的作用域,系统仅仅根据这些信息根本无法判断到底是调用哪个基类的成员,这时就必须通过作用域运算符来标识成员。,下面通过例子对上述的规则进行讨论。 例若有以下定义: class A public: void f(); ;,多继承,class B public: void f(); void g(); ;,class C:public A,public B public: void g(); void h(); ;,多继承,如果定义一个类C的对象c1,则对函数f()的访问c1.f()便具有二义性!,原因:是访问类A中的f()还是访问类B中的f() ?,解决方法:可用作用域运算符来消除二义性:c1.A:f()或c1.B:f()。,解决此二义性的另一种办法是在类C中定义一个同名成员f(),类C中的f()再根据需要来决定调用A:f()还是B:f(),还是两者皆有,这样根据同名覆盖的原则,c1.f()将调用C:f()。,同样,类C中成员函数调用f()也会出现二义性问题。例如:,void C:h() f(); ,这里存在二义性的访问,该函数应修改为void C:h() A:f(); 或void C:h() B:f(); 或 void C:h() A:f(); B:f() 。,这里值得说明的是,类B中有一成员函数g(),类C中也有一成员函数g(),这时c1.g()则不存在二义性,同名覆盖原则可对此做出最恰当的诠注。,多继承,在多继承可能出现二义性问题的另一种情况,就是基类之间本身具有继承关系。如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域运算符来进行惟一标识,而且必须用直接基类进行限定。,例如: class A public: int a; ;,class B1:public A private: int b1; ;,多继承,class B2:public A private: int b2; ;,class C:public B1,public B2 public: int f(); private: int c; ;,若定义C类的一个对象c1,则c1.a或c1.A:a的访问存在二义性,要消除此二义性,只能用直接基类进行限定:c1.B1:a或c1.B2:a。在类C的成员函数中,对a的访问要消除二义性,同样需要用直接基类进行限定。如:int C:f() return B1:a+B2:a; 。,多继承,例5.10 分析下列程序的输出结果。 #include class A public: A(int i) a=i; cout<<构造A类对象!n; void print() cout<<a<<endl; A() cout<<析构A类对象!n; private: int a; ;,class B1:public A public: B1(int i,int j):A(i) b1=j;cout<<构造B1类对象!n; void print() A:print(); cout<<b1<<endl; B1() cout<<析构B1类对象!n; private: int b1; ;,多继承,class B2:public A public: B2(int i,int j):A(i) b2=j; cout<<构造B2类对象!n; void print() A:print(); cout<<b2<<endl; B2() cout<<析构B2类对象!n; private: int b2; ;,class C:public B1,public B2 public: C(int i,int j,int k, int m,int n):B1(i,j),B2(k,m),c(n) cout<<构造C类对象!n; void print() B1:print(); B2:print(); cout<<c<<endl; C() cout<<析构C类对象!n; private: int c; ;,多继承,void main() C c1(1,2,3,4,5); c1.print(); ,虚基类,在类多继承的层次中,直接基类、间接基类与派生类组成了类继承的层次结构。在该层次结构中,派生类的对象都将包含基类子对象的拷贝,尤其是在上层路径中存在公共基类,那么这个公共基类将在派生类的对象中,通过多条路径产生多个基类子对象的拷贝,同一个函数名也会有多个映射。这样在派生类成员或对象对其进行访问时,就会产生二义性的问题。当然我们可以使用作用域运算符来惟一地标识它们,但另外一种更为有效的途径是可以将共公基类设置为虚基类,这时从不同途径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样就解决了同名成员的惟一标识问题。,一虚基类的引入和说明,引入虚基类的真正目的是为了解决多继承中所产生的二义性问题。,虚基类的声明是在派生类的声明过程中完成的,虚基类的说明格式如下:,virtual ,其中,virtual是声明虚基类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。在多继承的情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。声明了虚基类之后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存数据的拷贝。,虚基类,下面是虚基类说明的一个示例。,class A public: void f(); private: int a; ;,class B:virtual public A protected: int b; ;,class C:virtual public A protected: int c; ;,class D:public B,public C public: int g(); private: int d; ;,虚基类,从DAG图中可见不同继承路径的虚基类子对象被合并成为一个子对象。这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。这时,在类D的对象中只存在一个类A的子对象。,虚基类,这样,下面的引用是正确的:,D n; n.f(); /对f()引用是正确的,因不存在二义性问题 void D:g() f(); /正确,道理同上,同样下面的程序段也是正确的 :,D n; A *pa; pa=,其中,pa是指向类A对象的指针,n是类D的一个对象, A() ;,虚基类,class B:virtual public A public: B(const char *s1,const char *s2):A(s1) cout<<s2<<endl; ;,class C:virtual public A public: C(const char *s1,const char *s2):A(s1) cout<<s2<<endl; ;,虚基类,class D:public B,public C public: D(const char *s1,const char *s2,const char *s3,const char *s4) :C(s1,s3),B(s1,s2),A(s1) cout<<s4<<endl; ;,void main() D *ptr=new D(class A,class B,class C,class D); delete ptr; ,虚基类,运行结果:,分析:该程序中,在派生类B和C中使用了虚基类,使得建立的D类对象中只有一个虚基类的子对象,这里类D就是所谓的最远派生类。在派生类B、C、D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数的调用,但只有在建立D类对象时,类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,而且是先于其基类B、C的构造函数执行,而类D的基类B与C的构造函数成员初始化列表中所列出的虚基类构造函数调用则不被执行,和我们上述讨论的结论是吻合的。,虚基类,下面再给出一个日期与时间类的综合示例,作为对本章内容的总结。,#include #include #include typedef char string8080; class Date public: Date() Date(int y,int m,int d) SetDate(y,m,d); void SetDate(int y,int m,int d) Year=y; Month=m; Day=d; void GetStringDate(string80,虚基类,class Time public: Time() Time(int h,int m,int s) SetTime(h,m,s); void SetTime(int h,int m,int s) Hours=h; Minutes=m; Seconds=s; void GetStringTime(string80 ,虚基类,class TimeDate:public Date,public Time public: TimeDate():Date(),Time() TimeDate(int y,int mo,int d,int h,int mi,int s):Date(y,mo,d),Time(h,mi,s) void GetStringDT(string80 ,虚基类,void main() TimeDate date1,date2(2003,11,22,0,2,20); string80 DemoStr; date1.SetDate(2003,11,21); date1.SetTime(8,30,45); date1.GetStringDT(DemoStr); cout<<日期与时间为:<<DemoStr<<endl; date1.GetStringDate(DemoStr); cout<<日期为:<<DemoStr<<endl; date1.GetStringTime(DemoStr); co

    注意事项

    本文(类的继承与派生谭浩强C.ppt)为本站会员(小**)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开