《第章 继承性与派生类》由会员分享,可在线阅读,更多相关《第章 继承性与派生类(78页珍藏版)》请在金锄头文库上搜索。
1、继承性和派生类继承性和派生类l继承性是面向对象程序设计中最重要的机制l克服了传统程序设计方法对编写出来的程序无法重复使用l可以扩充和完善旧的程序设计以适应新的需求l为未来程序设计增添了新的资源1 基类和派生类基类和派生类l可以利用已有的数据类型来定义新的数据类型l所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员l称已存在的用来派生新类的类为基类,又称为父类l由已存在的类派生出的新类称为派生类,又称为子类单继承、多继承: 继承的结果-扩充l从一个基类派生的继承称为单继承 l从多个基类派生的继承称为多继承 单继承 多继承 1.1 派生类的定义格式派生类的定义格式l单继承的定义格式如
2、下:Class:;l多继承的定义格式如下:Class:,;l常使用如下三种关键字给予表示:public表示公有继承private表示私有继承protected表示保护继承1.2 派生类的三种继承方式派生类的三种继承方式l公有继承(public) 基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的l私有继承(private) 基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问l保护继承(protected) 基类的所有公有成员和保护成员都作为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有
3、的l系统的默认值是私有继承(private)。不同继承方式的基类和派生类特性不同继承方式的基类和派生类特性类成员类成员 对象对象 public public 对象对象 protected protected 对象对象 private private 对象对象类型类型 继承类继承类 继承类继承类 继承类继承类priv X X X X X X Xpriv X X X X X X Xprote X prote X prote X priv X prote X prote X prote X priv X pub V pub V prote X priv Xpub V pub V prote X pr
4、iv X protected : protected : 对派生类的成员来说,是共有的;其余为私有。对派生类的成员来说,是共有的;其余为私有。1.3 基类和派生类的关系基类和派生类的关系l任何一个类都可以派生出一个新类,派生类也可以再派生出新类l类A是类C的间接基类, 类B是类A的直接派生类基类与派生类之间的关系:可复用的软件构件l派生类是基类的具体化 l派生类是基类定义的延续 l派生类是基类的组合 2 单继承单继承 l每一个类可以有多个派生类l每一个派生类只能有一个基类从而形成树形结构2.1 成员访问权限的控制成员访问权限的控制 l公有继承publicl私有继承privatel保护继承pro
5、tected公有继承(public)公有继承方式创建的派生类对基类各种成员访问权限如下:基类公有成员相当于派生类的公有成员,即派生基类公有成员相当于派生类的公有成员,即派生类可以象访问自身公有成员一样访问从基类继承类可以象访问自身公有成员一样访问从基类继承的公有成员。的公有成员。基类保护成员相当于派生类的保护成员,即派生基类保护成员相当于派生类的保护成员,即派生类可以象访问自身的保护成员一样,访问基类的类可以象访问自身的保护成员一样,访问基类的保护成员。保护成员。对于基类的私有成员,派生类内部成员无法直接对于基类的私有成员,派生类内部成员无法直接访问。派生类使用者也无法通过派生类对象直接访问。
6、派生类使用者也无法通过派生类对象直接访问。访问。例例 分析程序中的访问权限 #includeclassApublic:voidf1();protected:intj1;private:inti1;classB:publicApublic:voidf2();protected:intj2;private:inti2;classC:publicBpublic:voidf3();回答下列问题: l派生类B中成员函数f2( )能否访问基类A中的成员:f1( ), i1 和 j1呢? l派生类B的对象b1能否访问基类A中的成员:f1( ), i1 和 j1呢? l派生类C中成员函数f3( )能否访问直接
7、基类B中的成员:f2( )和j2呢?能否访问间接基类A中的成员:f1( ), i1 和 j1 呢? l派生类C的对象c1能否访问直接基类B中的成员:f2( )和j2呢?能否访问间接基类A中的成员:f1( ), i1和j1呢? l从对(1)(4)问题的回答可得出什么结论? Ans:1.可以访问f1( )和j1,而不可以访问i1。 2.可以访问f1( ),而不可以访问j1和i1。 3.可以访问直接基类中的f2( )和j2以及间接基类中的f1( )和j1,而不可以访问i2和i1。 4.可以访问直接基类中的f2( )和间接基类中的f1( ),其它的都不可以访问。 5.在公有继承时,派生类的成员函数可访
8、问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。 私有继承 (private)派生类对基类各种成员访问权限如下:基类公有成员和保护成员都相当于派生类基类公有成员和保护成员都相当于派生类的私有成员,派生类只能通过自身的函数的私有成员,派生类只能通过自身的函数成员访问他们成员访问他们对于基类的私有成员,无论派生类内部成对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问。员或派生类使用者都无法直接访问。 例例 分析程序,分析程序,回答问题 #includeclassApublic:voidf(inti)coutiendl:voidg()coutgn;classB:
9、Apublic:voidh()couthn;A:f;voidmain()Bd1;d1.f(6);d1.g();d1.h();l回答下列问题:1.执行该程序时,哪个语句会出现编译错?为什么? 2.去掉出错语句后,执行该程序后输出结果如何? 3.程序中派生类B是从基类A继承来的,这种缺省继承方式是哪种继承方式? 4.派生类B中,A:f的含义是什么? 5.将派生类B的继承改为公有继承方式该程序输出什么结果? Ans:1d1.g();语句出现编译错误,因为B是以私有继承方式继承类A的,所以B类的对象不可访问A类的成员函数。2d1.g();语句注释后,执行该程序输出以下结果:6h3使用class关键字定
10、义类时,缺省的继承方式是private。4A:f;是将基类中的公有成员说明为派生类的公有成员。5将classB:A改为classB:publicA以后,输出如下:6gh保护继承(public)保护继承方式创建的派生类对基类各种成员访问权限如下:基类的公有成员和保护成员都相当于派生基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问他们员函数或其子类的成员函数访问他们对于基类的私有成员,无论派生类内部成对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问员或派生类使用者都无法直接访问例 分析程序,回
11、答问题 Include#includeclassApublic:A(constchar*nm)strcpy(name,nm);private:charname80;classB:publicApublic:B(constchar*nm):A(nm);voidPrintName()const;voidB:PrintName()constcoutnamenameendl;voidmain()Bb1(wangli);b1.PrintName();回答下列问题:1.执行该程序将会出现什么编译错? 2.对出现的编译错如何在访问权限上进行修改? 3.修改后使该程序通过编译,执行执行该程序后输出结果是什么?
12、 Ans:1 编译时出错行是:coutname:nameendl; 错误信息提示name是私有成员不能访问。2 在类A中,将private改写为protected。这样就可以通过编译。 派生类可访问基类的保护部分,并把它作为派生类的公有部分;但程序其他部分把name作为私有成员。例如在main中,不能运行 strcpy(s1,bi.name)3 执行修改后的该程序输出如下结果: wang li2.2 构造函数和析构函数构造函数和析构函数 1. 1. 构造函数构造函数 l派生类对象是由基类中说明的数据成员和派生类中说明的数据成员共同构成l基类中说明的数据成员和操作所构成的封装体称为基类子对象l派
13、生类的构造函数必须通过调用基类的构造函数类初始化基类子对象l在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须负责调用基类构造函数使基类的数据成员得以初始化。如果派生类中还有子对象时,还应包含对子对象初始化的构造函数。派生类构造函数l派生类构造函数的格式如下:():(),();l派生类构造函数的调用顺序如下: 基类的构造函数 子对象类的构造函数(如果有的话) 派生类构造函数 例例 #includeclassApublic:A()a=0;coutAsdefaultconstructorcalled.n;A(inti)a=i;coutAsconstructorcalled.n;A()
14、coutAsdestructorcalled.n;voidPrint()constcouta,;intGeta()returna;private:inta;classB:publicApublic:B()b=0;coutBsdefaultconstructorcalled.n;B(inti,intj,intk);B()coutBsdestrutorcalled.n;voidPrint();private:intb;Aaa;B:B(inti,intj,intk):A(i),aa(j)b=k;coutBsconstructorcalled.n;voidB:Print()A:Print();cout
15、b,aa.Geta()endl;voidmain()Bbb2;bb0=B(1,2,5);bb1=B(3,4,7);for(inti=0;i2;i+)bbi.Print();Ans:Asdefaultconstructorcalled.Asdefaultconstructorcalled.构造函数Bsdefaultconstructorcalled.Asdefaultconstructorcalled.Asdefaultconstructorcalled.构造函数Bsdefaultconstructorcalled.Asconstructorcalled.Asconstructorcalled.
16、Bsconstructorcalled.赋值Bsdestructorcalled.Asdestructorcalled.Asdestructorcalled.Asconstructorcalled.Asconstructorcalled.Bsconstructorcalled.赋值Bsdestructorcalled.Asdestructorcalled.Asdestructorcalled.1,5,23,7,4Bsdestructorcalled.Asdestructorcalled.Asdestructorcalled.Bsdestructorcalled.Asdestructorcall
17、ed.Asdestructorcalled.派生类的构造函数l再如:classPersonchar m_strName10;intm_nAge;public:Person(char*name,intage)strcpy(m_strName,name);m_nAge=age;coutconstructorofpersonm_strNameendl;Person()coutdeconstrutorofpersonm_strNameendl;classEmployee:publicPersonchar m_strDept20;PersonWang;public:Employee(char*name,
18、intage,char*dept,char*name1,intage1):Person(name,age),Wang(name1,age1)strcpy(m_strDept,dept);coutconstructorofEmployeeendl;Employee()coutdeconstrucorofEmployeeendl;2. 2. 析构函数析构函数l当对象被删除时,派生类的析构函数被执行l由于析构函数不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用l先执行派生类的析构函数,再执行基类的析构函数 例例 #includeclassMpublic:M()m1=m2=0;M(i
19、nti,intj)m1=i;m2=j;voidprint()coutm1,m2,:M()coutMsdestructorcalled.n;private:intm1,m2;classN:publicMpublic:N()n=0;N(inti,intj,intk);voidprint()M:print();coutnendl;N()coutNsdestructorcalled.n;private:intn;N:N(inti,intj,intk):M(i,j),n(k)voidmain()Nn1(5,6,7),n2(-2,-3,-4);n1.print();n2.print();Ans:5,6,7
20、-2,-3,-4,Nsdestructorcalled.Msdestructorcalled.Nsdestructorcalled.Msdestructorcalled.3.3.派生类构造函数应注意的问题派生类构造函数应注意的问题l派生类构造函数的定义中可以省略对基类构造函数的调用l条件是在基类中必须有缺省的构造函数或者根本没有定义构造函数(1) 例例 #includeclassApublic:A()a=0;A(inti)a=i;voidprint()couta,;private:inta;classB:publicApublic:B()b1=b2=0;B(inti)b1=i;b2=0;B(i
21、nti,intj,intk):A(i),b1(j),b2(k)voidprint()A:print();coutb1,b2endl;private:intb1,b2;voidmain()Bd1;Bd2(5);Bd3(4,5,6);/两个参数,编译出错d1.print();d2.print();d3.print();Ans:0,0,00,5,04,5,6(2)l当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径l在有的情况下,派生类构造函数的函数体可能为空,仅起到参数传递作用例如: classBpublic:B(inti,intj)b1=i;b2=j
22、;private:intb1,b2;classD:publicBpublic:D(inti,intj,intk,intl,intm);private:intd1;Bbb;D:D(inti,intj,intk,intl,intm):B(i,j),bb(k,l)d1=m;解释:l派生类D的构造函数有5个参数l前两个参数传递给基类B的构造函数l接着两个参数传递给子对象bb的类B的构造函数l最后一个参数是传递给派生类D的数据成员d12.3 子类型化和类型适应子类型化和类型适应 1. 1. 子类型化子类型化 l类型化的概念涉及到行为共享,它与继承有着密切的关系l有一个特定的类型S,当且仅当它至少提供了类
23、型T行为,则称类型S是类型T的子类型l子类型是类型之间一般和特殊的关系例: classApublic:voidPrint()onstcoutA:print()called.n;classB:publicApublic:voidf();类B公有继承了类A,类B是类A的一个子类型类B是类A的子类型,类B具备类A中的操作类A中的操作可以被用于操作类B的对象例: voidf1(constA&r)r.Print();voidmain()Bb;f1(b);执行该程序将会输出如下结果:A:Print()called.类B的对象b交给了处理类A的对象的函数f1( )进行处理。对类A的对象操作的函数,可以对类A
24、的子类的对象进行操作。子类型关系是不可逆的,子类型关系是不对称的。 2. 类型适应 l类型适应是指两种类型之间的关系l派生类的对象可以用于基类对象所能使用的场合,我们说派生类适应于基类l派生类对象的指针和引用也适应于基类对象的指针和引用l子类型化与类型适应是一致的。A类型是B类型的子类型,那么A类型必将适应于B类型。l子类型的重要性就在于减轻程序人员编写程序代码的负担l一个函数可以用于某类型的对象,则它也可用于该类型的各个子类型的对象l不必为处理这些子类型的对象去重载该函数 例例 #includeclassApublic:A()a=0;A(inti)a=i;voidprint()coutaen
25、dl;intgeta()returna;private:inta;classB:publicApublic;B()b=0;B(inti,intj):A(i),b(j)voidprint()A:print();coutbendl;private:intb;voidfun(A&d)coutd.geta()*10print();fun(bb);Ans:9190解释:aa=bb;pa=pb;合法bb=aa;pb=pa;非法3 多继承多继承 3.1 多继承的概念多继承的概念 l多继承可以看作是单继承的扩展l派生类与每个基类之间的关系仍可看作是一个单继承l多继承下派生类的定义格式如下:class:,;多继
26、承中派生类与多个基类之间关系多继承中派生类与多个基类之间关系例如:classA;classB;classC:publicA,publicB;派生类C具有两个基类(类A和类B)。派生类C的成员包含了基类A中成员和基类B中成员以及该类本身的成员。3.2 多继承的构造函数多继承的构造函数 l多继承的派生类的构造函数格式如下:():(),(),(),;l必须同时负责该派生类所有基类构造函数的调用l派生类的参数个数必须等于所有基类初始化所需的参数个数l执行顺序时先执行所有基类的构造函数,再执行派生类本身构造函数l处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数
27、中所定义的成员初始化列表的各项顺序无关例例 #includeclassB1public:B1(inti);b1=i;coutconstructorB1.iendl;voidprint()coutb1endl;private:intb1;classB2public:B2(inti);b2=i;coutconstructorB2.Iendl;voidprint()coutb2endl;private:intb2;classB3public:B3(inti);b3=i;coutcondtructorB3.iendl;intgetb3()returnb3;private:intb3;classA:pu
28、blicB2,publicB1public:A(inti,intj,intk,intl):B1(i),B2(j),bb(k)a=l;coutconstructorA.lendl;voidprint()B1:print();B2:print();couta,bb.getb3()endl;private:inta;B3bb;voidmain()Aaa(1,2,3,4);aa.print();Ans:constructorB2.2/B2构造函数,B2.b2=j=2constructorB1.1/B1构造函数,B1.b1=i=1constructorB3.3/生成B3的对象,B3.bb=k=3cons
29、tructorA.4/A构造函数,A.a=l=41/B1:Print2/B2:Print4,3/a,bb.getb3()3.3 3.3 二义性问题二义性问题l由于多继承情况下,可能造成对基类中成员的访问出现了不唯一的情况,则称为对基类成员访问的二义性问题例如: classApublic:voidf();classBpublic:voidf();voidg();classC:publicA,publicBpublic:voidg();voidf();解释:l如果定义一个类C的对象c1: C c1; 对函数f( )的访问 c1.f( );便有二义性 是访问类A中的f( ),还是访问类B中的f( )
30、呢l解决的方法:用成员名限定法来消除二义性,例如 c1.A:f( ); 或者 c1.B:f( );l最好的解决方法:在类C中定义一个同名成员f( ), 类C中的f( )再根据需要来决定调用A:f( ),还是B:f( ),还是两者皆有,这样,c1.f( )将调用C:f( )类类C C中成员函数调用中成员函数调用f( )f( )也会出现二义性也会出现二义性lvoidC:h()f();l该函数应该修改为:voidC:h()或者 voidC:h()或者 voidC:h()A:f();B:f();A:f();B:f();派生类的成员将支配基类中的同名成员派生类的成员将支配基类中的同名成员 l类B中有成员
31、函数g( ),类C中也有成员函数g( )c1.g();不存在二义性l它是指C:g( ),而不是指B:g( )。l两个g()函数,一个出现在基类B,一个出现在派生类C,规定派生类的成员将支配基类中的同名成员lDAG的图表示法例如:例如: classApublic:inta;classB1:publicAprivate:intb1;classB2:publicAprivate:intb2;classB2:publicAprivate:intb2;classC:publicB1,publicB2public:intf();private:intc;当派生类从多个基类派生,这些基类又有一个共同的基类,
32、可能会出现二义性l使用图表示如下:已知:C c1;l下面的两个访问都有二义性:下面的两个访问都有二义性: c1.a;c1.a; c1.A:a; c1.A:a;l而下面两个访问是正确的:而下面两个访问是正确的: c1c1.B1:a;.B1:a; c1c1.B2:a;.B2:a;l类类C C的成员函数的成员函数f( ) f( ) 可以消除二义性:可以消除二义性: int C:f( )int C:f( ) return B1:a + B2:a; return B1:a + B2:a; 例例7.9 7.9 #includeclassApublic:A(inti)a=i;coutcon.An;voidp
33、rint()coutaendl;A()coutdes.An;prinvate:inta;classB1:publicApublic:B1(inti,intj):A(i)b1=j;coutcon.B1n;voidprint()A:print();coutb1endl;B1()coutdes.B1n;private:intb1;classB2:publicApublic:B2(inti,intj):A(i)b2=j;coutcon.B2n;voidprint()A:print();coutb2endl;B2()coutdes.B2n;private:intb2;classC:publicB1,pu
34、blicB2public:C(inti,intj,intk,intl,intm):B1(i,j),B2(k,l),c(m)coutcon.Cn;voidprint()B1:print();B2:print();coutcendl;C()coutdes.Cn;private:intc;voidmain()Cc1(1,2,3,4,5);c1.print();Ans:con.Acon.B1con.Acon.B2con.C12345des.Cdes.B2des.Ades.B1des.A4 虚基类虚基类 l由于类A是派生类C两条继承路径上的一个公共基类,此公共基类将在派生类的对象产生多个基类子对象l基类
35、设定虚基类,可只产生一个虚基类,可只产生一个基类子对象4.1 虚基类的引入和说明虚基类的引入和说明 l引进虚基类的真正目的是为了解决二义性问题l虚基类说明格式如下:virtual例: classApublic:voidf();图示如下:protected:inta;classB:viutualpublicAprotected:intb;classC:virtualpublicBprotected:intc;classD:publicB,publicCpublic:intg();private:intd;消除二义性l不同继承路径的虚基类子对象被合并成为一个子对象l下面的引用都是正确的:Dn;n.
36、f();/对f()引用是正确的。voidD:g()f();/对f()引用是正确的。 l下面程序段是正确的:Dn;A*pa;pa=&n;派生类的对象只存在一个虚基类的子对象派生类的对象只存在一个虚基类的子对象l当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象l该指针被称为虚基类指针l各类的存储结构如下图所示:4.2 虚基类的构造函数虚基类的构造函数l由于派生类的对象中只有一个虚基类子对象,为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次l规定将在建立对象时所指定的类称为最派生类l虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初
37、始化的l如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用l如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象l用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的子对象只初始化一次l虚基类的构造函数先于非虚基类的构造函数的执行 例例 虚基类的派生类构造函数的调用虚基类的派生类构造函数的调用虚基类的派生类构造函数的调用虚基类的派生类构造函数的调用 #includeclassApublic:A(constchar*s
38、)coutsendl;A();classB:virtualpublicApublic:B(constchar*s1,constchar*s2):A(s1)couts2endl;classC:virtualpublicApulic:C(constchar*s1,constchar*s2):A(s1)couts2endl;classD:publicB,publicCpublic:D(constchar*s1,constchar*s2,constchar*s2,constchar*s4):B(s1,s2),C(s1,s3),A(s1)couts4endl;voidmain()D*ptr=newD(classA,classB,classC,classD);deleteptr;Ans:classAclassBclassCclassD