《继承性和派生类》PPT课件.ppt
继承性和派生类,继承性是面向对象程序设计中最重要的机制克服了传统程序设计方法对编写出来的程序无法重复使用可以扩充和完善旧的程序设计以适应新的需求 为未来程序设计增添了新的资源,1 基类和派生类,可以利用已有的数据类型来定义新的数据类型所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员称已存在的用来派生新类的类为基类,又称为父类由已存在的类派生出的新类称为派生类,又称为子类,单继承、多继承:继承的结果-扩充,从一个基类派生的继承称为单继承 从多个基类派生的继承称为多继承 单继承 多继承,1.1 派生类的定义格式,单继承的定义格式如下:Class:;多继承的定义格式如下:Class:,;常使用如下三种关键字给予表示:public 表示公有继承 private 表示私有继承 protected 表示保护继承,1.2 派生类的三种继承方式,公有继承(public)基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的私有继承(private)基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问保护继承(protected)基类的所有公有成员和保护成员都作为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的系统的默认值是私有继承(private)。,不同继承方式的基类和派生类特性,类成员 对象 public 对象 protected 对象 private 对象类型 继承类 继承类 继承类priv X X X X X X Xprote X prote X prote X priv X pub V pub V prote X priv X protected:对派生类的成员来说,是共有的;其余为私有。,1.3 基类和派生类的关系,任何一个类都可以派生出一个新类,派生类也可以再派生出新类类A是类C的间接基类,类B是类A的直接派生类基类与派生类之间的关系:可复用的软件构件派生类是基类的具体化 派生类是基类定义的延续 派生类是基类的组合,2 单继承,每一个类可以有多个派生类每一个派生类只能有一个基类从而形成树形结构,2.1 成员访问权限的控制,公有继承public私有继承private保护继承protected,公有继承(public),公有继承方式创建的派生类对基类各种成员访问权限如下:基类公有成员相当于派生类的公有成员,即派生类可以象访问自身公有成员一样访问从基类继承的公有成员。基类保护成员相当于派生类的保护成员,即派生类可以象访问自身的保护成员一样,访问基类的保护成员。对于基类的私有成员,派生类内部成员无法直接访问。派生类使用者也无法通过派生类对象直接访问。,例 分析程序中的访问权限#include class A public:void f 1();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呢?派生类B的对象b1能否访问基类A中的成员:f1(),i1 和 j1呢?派生类C中成员函数f3()能否访问直接基类B中的成员:f2()和j2呢?能否访问间接基类A中的成员:f1(),i1 和 j1 呢?派生类C的对象c1能否访问直接基类B中的成员:f2()和j2呢?能否访问间接基类A中的成员:f1(),i1和j1呢?从对(1)(4)问题的回答可得出什么结论?,Ans:,可以访问f1()和j1,而不可以访问i1。可以访问f1(),而不可以访问j1和i1。可以访问直接基类中的f2()和j2以及间接基类中的f1()和j1,而不可以访问i2和i1。可以访问直接基类中的f2()和间接基类中的f1(),其它的都不可以访问。在公有继承时,派生类的成员函数可访问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。,私有继承(private),派生类对基类各种成员访问权限如下:基类公有成员和保护成员都相当于派生类的私有成员,派生类只能通过自身的函数成员访问他们 对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问。,例分析程序,回答问题,#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;,void main()B d1;d1.f(6);d1.g();d1.h();,回答下列问题:执行该程序时,哪个语句会出现编译错?为什么?去掉出错语句后,执行该程序后输出结果如何?程序中派生类B是从基类A继承来的,这种缺省继承方式是哪种继承方式?派生类B中,A:f的含义是什么?将派生类B的继承改为公有继承方式该程序输出什么结果?,Ans:,1 d1.g();语句出现编译错误,因为B是以私有继承方式继承类A的,所以B类的对象不可访问A类的成员函数。2 d1.g();语句注释后,执行该程序输出以下结果:6 h3 使用class关键字定义类时,缺省的继承方式是private。4 A:f;是将基类中的公有成员说明为派生类的公有成员。5 将class B:A改为class B:public A以后,输出如下:6 g h,保护继承(public),保护继承方式创建的派生类对基类各种成员访问权限如下:基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问他们对于基类的私有成员,无论派生类内部成员或派生类使用者都无法直接访问,例 分析程序,回答问题,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;void main()B b1(wang li);b1.PrintName();,回答下列问题:,执行该程序将会出现什么编译错?对出现的编译错如何在访问权限上进行修改?修改后使该程序通过编译,执行执行该程序后输出结果是什么?,Ans:,1 编译时出错行是:coutname:nameendl;错误信息提示name是私有成员不能访问。2 在类A中,将private改写为protected。这样就可以通过编译。派生类可访问基类的保护部分,并把它作为派生类的公有部分;但程序其他部分把name作为私有成员。例如在main中,不能运行 strcpy(s1,bi.name)3 执行修改后的该程序输出如下结果:wang li,2.2 构造函数和析构函数,1.构造函数 派生类对象是由基类中说明的数据成员和派生类中说明的数据成员共同构成基类中说明的数据成员和操作所构成的封装体称为基类子对象派生类的构造函数必须通过调用基类的构造函数类初始化基类子对象在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须负责调用基类构造函数使基类的数据成员得以初始化。如果派生类中还有子对象时,还应包含对子对象初始化的构造函数。,派生类构造函数,派生类构造函数的格式如下:():(),();派生类构造函数的调用顺序如下:基类的构造函数 子对象类的构造函数(如果有的话)派生类构造函数,例,#include class A public:A()a=0;coutAs default constructor called.n;A(int i)a=i;coutAs constructor called.n;A()cout As destructor called.n;void Print()const cout a,;int Geta()return a;private:int a;,class B:public Apublic:B()b=0;cout Bs default constructor called.n;B(int i,int j,int k);B()cout Bs destrutor called.n;void Print();private:int b;A aa;B:B(int i,int j,int k):A(i),aa(j)b=k;cout Bs constructor called.n;,void B:Print()A:Print();cout b,aa.Geta()endl;void main()B bb2;bb0=B(1,2,5);bb1=B(3,4,7);for(int i=0;i2;i+)bbi.Print();,Ans:,As default constructor called.As default constructor called.构造函数Bs default constructor called.As default constructor called.As default constructor called.构造函数Bs default constructor called.As constructor called.As constructor called.Bs constructor called.赋值Bs destructor called.As destructor called.As destructor called.As constructor called.As constructor called.Bs constructor called.赋值Bs destructor called.As destructor called.As destructor called.1,5,23,7,4Bs destructor called.As destructor called.As destructor called.Bs destructor called.As destructor called.As destructor called.,派生类的构造函数,再如:class Person char m_strName10;intm_nAge;public:Person(char*name,int age)strcpy(m_strName,name);m_nAge=age;coutconstructor of personm_strNameendl;Person()coutdeconstrutor of personm_strNameendl;,class Employee:public Personchar m_strDept20;Person Wang;public:Employee(char*name,int age,char*dept,char*name1,int age1):Person(name,age),Wang(name1,age1)strcpy(m_strDept,dept);coutconstructor of Employeeendl;Employee()coutdeconstrucor of Employeeendl;,2.析构函数,当对象被删除时,派生类的析构函数被执行由于析构函数不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用先执行派生类的析构函数,再执行基类的析构函数,例,#include class M public:M()m1=m2=0;M(int i,int j)m1=i;m2=j;void print()cout m1,m2,:M()cout Ms destructor called.n;private:int m1,m2;,class N:public M public:N()n=0;N(int i,int j,int k);void print()M:print();cout n endl;N()cout Ns destructor called.n;private:int n;N:N(int i,int j,int k):M(i,j),n(k)void main()N n1(5,6,7),n2(-2,-3,-4);n1.print();n2.print();,Ans:,5,6,7-2,-3,-4,Ns destructor called.Ms destructor called.Ns destructor called.Ms destructor called.,派生类构造函数应注意的问题,派生类构造函数的定义中可以省略对基类构造函数的调用条件是在基类中必须有缺省的构造函数或者根本没有定义构造函数,(1),例,#include class A public:A()a=0;A(int i)a=i;void print()cout a,;private:int a;,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;void main()B d1;B d2(5);B d3(4,5,6);/两个参数,编译出错 d1.print();d2.print();d3.print();,Ans:,0,0,00,5,04,5,6,(2),当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径 在有的情况下,派生类构造函数的函数体可能为空,仅起到参数传递作用,例如:,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);private:int d1;B bb;D:D(int i,int j,int k,int l,int m):B(i,j),bb(k,l)d1=m;,解释:,派生类D的构造函数有5个参数前两个参数传递给基类B的构造函数接着两个参数传递给子对象bb的类B的构造函数最后一个参数是传递给派生类D的数据成员d1,2.3 子类型化和类型适应,1.子类型化 类型化的概念涉及到行为共享,它与继承有着密切的关系有一个特定的类型S,当且仅当它至少提供了类型T行为,则称类型S是类型T的子类型子类型是类型之间一般和特殊的关系,例:,class A public:void Print()onst cout A:print()called.n;class B:public A public:void f();类 B 公有继承了类 A,类 B 是类 A 的一个子类型类 B 是类A的子类型,类 B 具备类A中的操作类 A 中的操作可以被用于操作类 B 的对象,例:,void f1(const A 执行该程序将会输出如下结果:A:Print()called.类B的对象b交给了处理类A的对象的函数f1()进行处理。对类A的对象操作的函数,可以对类A的子类的对象进行操作。子类型关系是不可逆的,子类型关系是不对称的。,2.类型适应,类型适应是指两种类型之间的关系派生类的对象可以用于基类对象所能使用的场合,我们说派生类适应于基类派生类对象的指针和引用也适应于基类对象的指针和引用子类型化与类型适应是一致的。A类型是B类型的子类型,那么A类型必将适应于B类型。子类型的重要性就在于减轻程序人员编写程序代码的负担一个函数可以用于某类型的对象,则它也可用于该类型的各个子类型的对象不必为处理这些子类型的对象去重载该函数,例,#include class A public:A()a=0;A(int i)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,Ans:,9190解释:aa=bb;pa=pb;合法bb=aa;pb=pa;非法,3 多继承,3.1 多继承的概念 多继承可以看作是单继承的扩展派生类与每个基类之间的关系仍可看作是一个单继承多继承下派生类的定义格式如下:class:,;,多继承中派生类与多个基类之间关系,例如:class A;class B;class C:public A,public B;派生类C具有两个基类(类A和类B)。派生类C的成员包含了基类A中成员和基类B中成员以及该类本身的成员。,3.2 多继承的构造函数,多继承的派生类的构造函数格式如下:():(),(),(),;必须同时负责该派生类所有基类构造函数的调用派生类的参数个数必须等于所有基类初始化所需的参数个数执行顺序时先执行所有基类的构造函数,再执行派生类本身构造函数处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序无关,例,#include class B1 public:B1(int i);b1=i;cout constructor B1.i endl;void print()cout b1 endl;private:int b1;,class B2 public:B2(int i);b2=i;cout constructor B2.I endl;void print()cout b2 endl;private:int b2;class B3 public:B3(int i);b3=i;cout condtructor B3.i endl;int getb3()return b3;private:int b3;,class A:public B2,public B1 public:A(int i,int j,int k,int l):B1(i),B2(j),bb(k)a=l;cout constructor A.l 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();,Ans:,constructor B2.2/B2构造函数,B2.b2=j=2constructor B1.1/B1构造函数,B1.b1=i=1constructor B3.3/生成B3的对象,B3.bb=k=3constructor A.4/A构造函数,A.a=l=41/B1:Print2/B2:Print4,3/a,bb.getb3(),3.3 二义性问题,由于多继承情况下,可能造成对基类中成员的访问出现了不唯一的情况,则称为对基类成员访问的二义性问题,例如:,class A public:void f();class B public:void f();void g();class C:public A,public B public:void g();void f();,解释:,如果定义一个类C的对象c1: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()或者 void C:h()或者 void C:h()A:f();B:f();A:f();B:f();,派生类的成员将支配基类中的同名成员,类B中有成员函数g(),类C中也有成员函数g()c1.g();不存在二义性它是指C:g(),而不是指B:g()。两个g()函数,一个出现在基类B,一个出现在派生类C,规定派生类的成员将支配基类中的同名成员DAG的图表示法,例如:,class A public:int a;class B1:public A private:int b1;class B2:public A private:int b2;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的成员函数f()可以消除二义性:int C:f()return B1:a+B2:a;,例7.9,#include class A public:A(int i)a=i;cout con.An;void print()cout a endl;A()cout des.An;prinvate:int a;,class B1:public A public:B1(int i,int j):A(i)b1=j;cout con.B1n;void print()A:print();cout b1 endl;B1()cout des.B1n;private:int b1;class B2:public A public:B2(int i,int j):A(i)b2=j;cout con.B2n;void print()A:print();cout b2 endl;B2()cout des.B2n;private:int b2;,class C:public B1,public B2 public:C(int i,int j,int k,int l,int m):B1(i,j),B2(k,l),c(m)cout con.Cn;void print()B1:print();B2:print();cout c endl;C()cout des.Cn;private:int c;void main()C c1(1,2,3,4,5);c1.print();,Ans:,con.Acon.B1con.Acon.B2con.C12345des.Cdes.B2des.Ades.B1des.A,4 虚基类,由于类 A 是派生类 C 两条继承路径上的一个公共基类,此公共基类将在派生类的对象产生多个基类子对象基类设定虚基类,可只产生一个基类子对象,4.1 虚基类的引入和说明,引进虚基类的真正目的是为了解决二义性问题虚基类说明格式如下:virtual,例:,class A public:void f();图示如下:protected:int a;class B:viutual public A protected:int b;class C:virtual public B protected:int c;class D:public B,public C public:int g();private:int d;,消除二义性,不同继承路径的虚基类子对象被合并成为一个子对象下面的引用都是正确的:D n;n.f();/对f()引用是正确的。void D:g()f();/对f()引用是正确的。下面程序段是正确的:D n;A*pa;pa=,派生类的对象只存在一个虚基类的子对象,当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象 该指针被称为虚基类指针 各类的存储结构如下图所示:,4.2 虚基类的构造函数,由于派生类的对象中只有一个虚基类子对象,为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次规定将在建立对象时所指定的类称为最派生类虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的 如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用 如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象 用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的子对象只初始化一次虚基类的构造函数先于非虚基类的构造函数的执行,例 虚基类的派生类构造函数的调用,#include class A public:A(const char*s)cout s endl;A();class B:virtual public A public:B(const char*s1,const char*s2):A(s1)cout s2 endl;,class C:virtual public A pulic: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*s2,const char*s4):B(s1,s2),C(s1,s3),A(s1)cout s4 endl;void main()D*ptr=new D(class A,class B,class C,class D);delete ptr;,Ans:,class Aclass Bclass Cclass D,