1、第四章 派生类与继承,1,主要内容:1、派生类的概念 2、派生类的构造函数和析构函数 3、调整基类成员在派生类中的访问属性的其他方法 4、多重继承 5、基类与派生类对象之间的赋值兼容关系,4.1派生类的概念,教学重点、难点: 1、派生类的定义 2、基类成员在派生类中的访问属性 3、派生类对基类成员的访问规则,继承性是一个非常自然的概念。 例如:儿子长的像父亲,那么说明儿子继承了父亲的特征。儿子继承父亲的高个子,宽肩膀,但俩人不是完全一样的,比如儿子的眼睛比父亲的眼睛大。这形成了一个层次关系:,父亲,儿子,父亲是父类(也叫基类),儿子是父亲的子类,儿子继承了父亲的一些基本特征,并且还具有自己特有
2、的特征,儿子类是从父亲类那里派生出来的,所以把他叫做父亲的子类(也叫派生类)。,继承是面向对象程序设计的重要特性之一,它允许在已有类的基础上创建新的类,新类可以从一个或多个已有类中继承数据成员和成员函数,而且可以重新定义或加进新的数据成员和成员函数,从而形成类的层次或等级。 继承的作用:避免共用代码的重复开发。,4.1.1为什么要使用继承?,class studentprivate:string stu_no;string name;string sex;public:void print1(); ;,class ustudentprivate:string stu_no;string nam
3、e;string sex;string department;float score;public:void print2(); ;,3,比较两个类,从现有类出发建立新的类,使新类继承老类的特点和功能,并且又加上自己的特点和功能的程序设计方法,是面向对象程序设计的继承技术。老的类叫基类(父类)新的类叫派生类(子类),2,C+中有两种继承:单一继承和多重继承。 对于单一继承派生类只能有一个基类; 对于多重继承派生类可以有多个基类。,4.1.2 派生类的声明,派生类的声明: class 派生类名 : 继承方式 基类名 /派生类新增的数据成员和成员函数 ;,4,“基类名”是一个已经声明的类的名称,
4、“派生类名”是继承原有基类的特征而生成的新类的名称。,继承方式可为3种: (1)公有继承(用public) (2)私有继承(用private或缺省) (3)保护继承(用protected ),class studentprivate:string stu_no;string name;string sex;public:student(string no,string na,string s)stu_no=no;name=na;sex=s;void print1()coutstu_no“ “ name “ “sexendl; ;,class ustudent:public student pr
5、ivate:string department;double score;public:ustudent(string d,double sc):student(,)department=d;score=sc;void print2()coutdepartment“ “scoreendl; ;,int main( ) ustudent m;m.init1(“2001“,“小王“,“男“);m.init2(“计算机“,410.5);m.print1();m.print2();system(“PAUSE“);return 0; ,4.1.3派生类的构成,构造派生类包括如下3部分工作: (1) 派生
6、类从基类接收成员类继承中,派生类把基类的全部成员(除构造函数与析构函数之外)接收过来 (2) 调整从基类接收来的成员派生类可以调整基类成员在派生类中的访问属性,派生类可以对基类成员进行重新定义 (3) 在派生类中增加新的成员在派生类中增加新的成员体现了派生类对基类功能的扩展,是继承和派生机制的核心。,4.1.4基类成员在派生类中的访问属性,派生类可以继承基类中除了构造函数和析构函数之外的成员,但是这些成员的访问属性在派生过程中是可以调整的。从基类继承来的成员在派生类中的访问属性是由继承方式控制的。不同的继承方式导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。,在派生类中,从基
7、类继承来的成员可以按访问属性划分为4种: (1)不可直接访问的成员(2)private成员(3)protected成员(4)public成员,基类成员的访问属性有3种:(1)private成员(2)protected成员(3)public成员,保护成员的作用:私有(private)和保护(protected)这两个关键字的性质很相似,一旦用其中一个对类成员进行了定义,那外部对这个类成员是无法访问的,但在涉及到派生时,他们在派生类里的属性就改变了。 1. 私有成员在派生类中是无权直接访问的,只能通过调用基类中的公有成员函数的方式实现。 2. 一定要直接访问基类中的私有成员,可以把这些成员说明为保
8、护成员protected。,基类成员在派生类中的访问属性,练习题,1、有几种继承方式?每种继承方式下基类成 员在派生类中的访问属性如何? 2、派生类能否直接访问基类的私有成员?若 否,应该如何实现? 3、保护成员有哪些特性?保护成员被公有继承和私有继承后的访问属性如何?,4.1.5 派生类对基类成员的访问规则,基类成员可以有public,protected,private三种访问属性,基类的成员函数可以访问基类中其他成员,但是类外通过基类对象,只能访问该类的公有成员。,派生类的成员函数可以访问从基类继承来的成员以外还可以访问派生类自己新增加的成员,但是在派生类外通过派生类的对象,就只能访问该类
9、的公有成员。,派生类对基类成员的访问形式主要有以下两种: (1)内部访问。由派生类中新增成员函数对基类继承来的成员的访问。 (2)对象访问。在派生类外部,通过派生类的对象对从基类继承来的 成员的访问。三种继承方式: (1)私有继承 (2)公有继承 (3)保护继承,1. 私有继承的访问规则,基类的public成员和protected成员相当于派生类的private成员。类内:派生类的其他成员可以访问它类外:通过派生类的对象无法访问。基类的private成员在派生类中是属于不可直接访问的成员。类内:不可访问类外:不可访问,5,私有继承的访问规则,#include class baseint x;p
10、ublic:void setx(int n) x=n; void showx() cout x endl; ; class derived : private base int y;public:void setxy(int n, int m) setx(n); y=m; void showxy() cout x y endl; / 非法 ;,int main() derived obj;obj.setxy(10,20);obj.showxy();return 0;,class baseprotected:int x;public:void setx(int n) x=n; void show
11、x() cout x endl; ;,6,class derived1 : private base protected:int y;public:void setxy(int m,int n) x=m; y=n; void showxy() coutxendl;cout y endl; ;,int main( ) derived1 m; m.setxy(4,5); m.showxy(); derived2 n; n.setxyz(1,2,3); n.showxyz(); system(“pause“);return 0; ,class derived2 : private derived1
12、private:int z;public:void setxyz(int m,int n,int k) setxy(m,n)y=n;z=k; void showxyz() showxy();coutzendl; ;,2. 公有继承的访问规则,基类的public成员和protected成员相当于派生类的public成员和protected成员类内:派生类的其他成员可以访问它类外:通过派生类的对象只能访问public成员。基类的private成员在派生类中是属于不可直接访问的成员。类内:不可访问类外:不可访问,7,公有继承的访问规则,例4.3 #include using namespace st
13、d; class base /定义基类int x;public:void setx(int n) x=n; void showx() cout x endl; ; class derived : public base /定义公有派生类int y;public:void sety(int n) y=n; void showy() cout y endl; ;,main() derived obj;obj.setx(10); /合法obj.sety(20); /合法obj.showx(); /合法obj.showy(); /合法return 0; ,3、保护继承的访问规则,基类的public成员
14、和protected成员相当于派生类的protected成员类内:派生类的其他成员可以访问它 类外:通过派生类的对象无法访问 基类的private成员在派生类中是属于不可直接访问的成员。类内:不可访问 类外:不可访问,保护继承的访问规则,class base private:int x; protected:int y; public:int z;void setx(int i)x=i;int getx()return x; ;,class derived:protected base private:int m; protected:int n; public:int p;void seta
15、ll(int a,int b,int c,int d,int e,int f);void show(); ;,void derived:setall(int a,int b,int c,int d,int e,int f) x=a;y=b;z=c;m=d;n=e;p=f; void derived:show() cout“x=“xendl; cout“x=“getx()endl; cout“y=“yendl; cout“z=“zendl; cout“m=“mendl; cout“n=“nendl; ,main() derived obj; obj.setall(1,2,3,4,5,6); ob
16、j.show(); cout“p=“obj.pendl; ,小结:,继承性是面向对象程序设计语言的重要特性之一,继承性避免共用代码的重复开发。 派生类对基类的集成方式可分为public、 protected、private。 每一种继承方式导致基类成员在派生类种的访问属性有所不同。 派生类对象对基类成员的访问方式也有所不同。,思考题:P188课后8,9题。 作业:P197课后22题。,教学内容: (1)派生类的构造函数和析构函数的执行顺序。 (2)派生类构造函数的构造规则 (3)派生类析构函数的构造规则,4.2 派生类的构造函数和析构函数,教学重点: (1)派生类的构造函数和析构函数的执行顺序
17、。 (2)派生类构造函数的构造规则 (3)派生类析构函数的构造规则 教学难点:派生类构造函数的构造规则,派生类继承了基类的成员,实现了代码的重用,但派生类无法继承基类的构造函数和析构函数,派生类中,如果对派生类新增的成员进行初始化,就需要加入派生类的构造函数。与此同时,对所有从基类继承来的成员的初始化工作,还是由基类的构造函数完成,但是我们必须在派生类中对基类的构造函数所需要的参数进行设置。同样,对撤销派生类对象时的扫尾、清理工作也需要加入新的析构函数来完成。当一个派生类有构造函数时,则它必须对自己的数据成员和继承而来的基类数据成员初始化,这和前面(第三章)讲的类定义中有对象作为数据成员类似。
18、因此需要由派生类的构造函数调用基类的构造函数来完成。,16,4.2.1 派生类的构造函数和析构函数执行顺序,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。,17,#include using namespace std; class base public :base( ) cout “执行 base 类的构造函数n“; base( ) cout “执行 base 类的析构函数n“; ; class derived:public base public :derived( ):base( ) co
19、ut “执行 derived 类的构造函数n“; derived( ) cout “执行 derived 类的析构函数n“; ;,main( ) derived op; return 0; ,4.2.2 派生类的构造函数和析构函数的构造规则,1.简单派生类的构造函数(1)当基类的构造函数没有参数,或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。(2)当基类构造函数含有参数时,必须定义派生类的构造函数。 格式为:派生类的构造函数名(参数总表):基类的构造函数名(参数表) / 其中,基类构造函数参数,来源于派生类的构造函数的参数表。,18,#include #incl
20、ude using namespace std; class student public :student(int number1,string name1,float score1)number=number1;name=name1;score=score1;void print()cout“number:“numberendl;cout“name:“nameendl;cout“score:“scoreendl;protected:int number;string name;float score; ;,/派生类ustudent class ustudent:public student
21、 public :ustudent(int number1,string name1,float score1,string major1):student(number1,name1,score1)major=major1;void print1()print();cout“major:“majorendl;private:string major; ;,main( ) ustudent stu(20100001,“张志“,95,“计算机科学与技术“); stu.print1(); system(“pause“); return 0; ,2.含有对象成员的派生类的构造函数,其构造函数的格式为
22、:,派生类的构造函数名(参数总表):基类的构造函数名(参数表),对象成员1(参数表),.,对象成员n(参数表) /派生类新增成员的初始化语句 定义派生类对象时构造函数执行顺序: 调用基类的构造函数,对基类数据成员初始化; 调用内嵌对象成员构造函数,对内嵌对象成员的数据成员初始化; 执行派生类的构造函数体,对派生类的数据成员初始化;当撤消派生类对象时,析构函数执行顺序正好相反。,19,#include using namespace std; class base int x;public:base(int i) /基类的构造函数 cout “执行 base 基类的构造函数n“;x=i;base
23、() cout “执行 base 基类的析构函数n“; void show() cout “x=“ x endl; ; class derived:public base base d;public :derived(int i):base(i),d(i) /派生类的构造函数 cout “执行 derived 派生类的构造函数n“; derived() cout “执行 derived 派生类的析构函数n“; ;,main() derived obj(5);obj.show();system(“pause“);return 0; ,(1)在派生类中含有多个内嵌对象成员时,调用内嵌对象成员的构造
24、函数由它们在类中声明的顺序确定。,#include #include using namespace std; class A public :A()cout“调用A类的构造函数“endl;A()cout“调用A类的析构函数“endl; class B public :B()cout“调用B类的构造函数“endl;B()cout“调用B类的析构函数“endl; ;,class C:public Bpublic:C()cout“调用C类的构造函数“endl; C()cout“调用C类的析构函数“endl;private:A m;B n; ; main() C O;system(“pause“);
25、return 0; ,(2)如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化。以此类推。,#include #include using namespace std; class A public :A(int a)x=a;cout“调用A类的构造函数“endl;A()cout“调用A类的析构函数“endl;private:int x; ;,class B:public Apublic :B(int i,int j):A(i)y=j;cout“调用B类的构造函数“endl;B()cout“调用B类的析构函数“endl;private:int y; ; class C
26、:public Bpublic:C(int m,int n,int k):B(m,n)z=k;cout“调用C类的构造函数“endl; C()cout“调用C类的析构函数“endl;private:int z; ;,main() C p(3,4,5);system(“pause“);return 0; ,3.派生类的析构函数,派生类中可以根据需要定义自己的析构函数,用来对派生类中的所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。由于析构函数不带参数,在派生类中是否自定义析构函数与它所属基类的析构函数无关。在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行
27、清理。析构函数的执行顺序与构造函数相反:先执行派生类的析构函数,再执行基类的析构函数。,#include using namespace std; class Apublic:A()cout“调用A类的构造函数“endl;A()cout“调用A类的析构函数“endl; ; class B:public Apublic:B()cout“调用B类的构造函数“endl;B()cout“调用B类的析构函数“endl; ; main() B b; system(“pause“); return 0;,本节课知识点:,1、同名成员 2、访问声明 3、多重继承(1)多重继承的定义(2)多重继承的构造函数的构
28、造规则 4、虚基类,#include #include class personprivate:char name10;int age;char sex;public:person(char n,int a,char s)strcpy(name,n);age=a;sex=s;void print()coutname“ “age “ “sexendl; ;,class employee : public person private:char department20;float salary;public: employee(char n,int a,char s,char d,float s
29、a):person(n,a,s)strcpy(department,d);salary=sa;void print() coutdepartmentsalaryendl; ;,void main() employee m(“王“,20,f,“人事“,2340); m.print(); ,4.3 调整基类成员在派生类中的访问属性的其他方法,4.3.1 同名成员当派生类与基类中有相同成员时: 若未强行指名,则通过派生类对象使用的是派生类中的同名成员 如要通过派生类对象访问基类中被覆盖的同名成员应使用基类名限定。必须在该成员名之前加上基类名和作用域标识符“:”。,class X public: in
30、t f(); class Y : public X public: int f();int g();,#include void main( ) X a;Y b;int i=a.f();int j=b.f();cout “i=“ i endl;cout “j=“ j endl;,这里 b.f() 用的是 Y 类的 f() 还是 X 类的 f()? 用的是 Y 类的 f(),编译器会自动从继承底向上搜索,这叫作支配规则。如果一定要用 X 类的 f(),则可写成 b.X:f(); X:叫作用域分辨符。,对于私有继承、基类的公有成员函数变成了派生类的私有成员函数。 这时派生类的对象无法直接调用基类的
31、成员函数,而只能通过调用派生类的成员函数间接的调用基类的成员函数。,4.3.2 访问声明,#include class A private:int x; public:A(int x1)x=x1;void print()cout“x=“x; ;,class B:private A private:int y; public:B(int x1,int y1):A(x1)y=y1;void print2()print(); ;,main() B b(10,20); b.print2(); return 0; ,A:print;,b.print();,有时程序员希望基类的个别成员还能被派生类的对象直
32、接访问,而不通过派生类的公有成员函数间接访问,访问声明可以解决此类问题。访问声明可个别调整基类的某些成员在派生类中保持原来的访问属性。 访问声明的方法:把基类的保护成员或公有成员直接写至私有派生类定义式的同名段中,同时给成员名冠以基类名和作用域标识符“:”。,注意(1)数据成员也可以使用访问声明。 (2)访问声明中只含不带类型和参数的函数名和变量名。 (3)访问声明不能改变类成员原来在基类中的成员性质。 (4)对于基类中的重载函数名,访问声明将对基类中的多有同名函数起作用。,4.4多重继承,当一个派生类具有多个基类时,这种派生方法称为多重继承。4.4.1 多重继承的声明多重继承的声明格式如下:
33、class 派生类名: 派生方式1 基类名1, . , 派生方式n 基类名n /派生类新增的数据成员和成员函数;派生类继承了基类1 到 基类n的所有数据成员和成员函数,其访问权限规则与单继承情况一样,多继承可看成单一继承的扩展。,20,21,#include class X int a;public:void setX(int x) a=x; void showX() cout “a=“aendl; ;,class Y int b;public:void setY(int x) b=x; void showY() cout “b=“bendl; ;,class Z: public X, pri
34、vate Y int c;public:void setZ(int x, int y) c=x; setY(y); void showZ() showY(); cout“c=“cendl; void setZ(int x, int y, int z) a=x; setY(y); c=z; void showZabc() cout“a=“aendl;showY();cout“c=“cendl; ;,main() Z obj;obj.setX(3);obj.showX();obj.setY();obj.showY();obj.setZ(6,8);obj.showZ(); ,注意:真正生产二义性的情
35、况是,一个派生类从多个基类派生,而这些基类又有相同的成员,则在派生类访问这个共同基类的成员时会产生二义性。,class A public: void fun() ;class B public: void fun() ; void gun() ;class C: public A, public B void gun() ;void hun() ;若有对象 C obj ; obj.fun() 是使用 A 类的 fun() 呢? 还是 B 类的 fun()? 编译器无法自动确定,因为对 C 类来说 A 类和 B 类是同一级,我们可以用成员名限定(作用域分辨操作符)来人为的消去二义性。obj.A:
36、fun(); obj.B:fun();,25,4.3.2 多重继承的构造函数和析构函数,设类Y从X1,X2,Xn类派生(当只有一个X1时为单继承,否则为多继承),则派生类Y的构造函数Y的一般形式为:Y:Y(参数表0):X1(参数表1),X2(参数表2),.,Xn(参数表n) / 其构造函数的调用顺序为先X1,X2,.,Xn(从左向右),最后为Y析构函数的执行顺序和构造函数执行顺序正好向反。,26,#include class X int a;public:X(int sa) a=sa; /X类构造函数int getX() return a; ;class Y int b;public:Y(in
37、t sb) b=sb; /Y类构造函数int getY() return b; ;,void main() Z obj(2,4,6);int ma=obj.getX(); cout “a=“ ma endl;int mb=obj.getY(); cout “b=“ mb endl;int mc=obj.getZ(); cout “c=“ mc endl; ,27,class Z:public X, private Y int c;public:Z(int sa, int sb, int sc):X(sa),Y(sb) c=sc; /Z类构造函数int getZ( ) return c; int
38、 getY( ) return Y:getY( ); ;,4.3.3 虚基类,1. 为什么要引入虚基类。一个派生类从多个基类派生,而这些基类又有一个相同的基类,则在派生类访问这个共同基类的成员时会产生二义性。虽然我们可以用“作用域分辨操作符”区别,但这时基类的成员有两个副本(拷贝)。,28,例如: #includeclass base public: int b;class base1:public base ;class base2:public base ;class derived :public base1, public base2 public:int fun() ; 程序运行结果
39、是path base1=8path base2=15base 类中的成员 b 有二个副本, 一个放 8,一个放 15。,void main( ) derived d;d.base1:b=8;d.base2:b=15;cout “d.base2:bendl;,2. 虚基类的概念,如果希望基类只有一个拷贝,则在定义时把基类说明成虚基类,加上virtual: #include class base public: int b;class base1:virtual public base ;class base2:virtual public base ;class derived :public
40、base1, public base2 public:int fun() ;这时程序运行结果是path base1=15path base2=15这时也没有了二义性,可以写成 int i=d.b;,void main( ) derived d;d.base1:b=8;d.base2:b=15;cout “d.base2:bendl;,29,#include class baseprotected:int a;public:base ( ) a=5; ; class base1:public base public:base1( ) cout “base1 a=“aendl; ; class b
41、ase2:public base public:base2( ) cout “base2 a=“aendl; ; class derived : public base1, public base2 public:derived( ) cout “derived a=“aendl; ;,int main( ) derived obj;return 0; ,30,#include class baseprotected:int a;public:base() a=5; ; class base1:virtual public base public:base1() cout “base1 a=“
42、aendl; ; class base2:virtual public base public:base2() cout “base2 a=“aendl; ; class deeived : public base1, public base2 public:derived() cout “derived a=“aendl; ;,int main() derived obj;return 0; ,31,虚基类的初始化,调用各类构造函数的顺序原则如下:(1) 同一层次中先调用虚基类的构造函数(不论它在何处),再是非虚基类。(2) 若有多个虚基类在同一层次,则按它们声明的次序。(3) 若虚基类前又
43、由非虚基类派生而来,则仍先非虚基类,再这虚基类。class A ;class B ;class C: virtual public A, virtual public B ;class D: virtual public B, virtual public A ;class E: public C, public D . ;则建立 E 的对象时:先 C 的 A 构造函数,C 的 B ,C再 D ( D 的 A,B是虚只一个副本 C 中已做)最后是 E,32,#include class base int a;public:base(int sa) /base类构造函数 a=sa; cout“构
44、造 base 类“endl; ; class base1:virtual public base int b;public:base1(int sa, int sb):base(sa) /base1类构造函数 b=sb; cout“构造 base1 类“endl; ; class base2:virtual public base int c;public:base2(int sa, int sc):base(sa) /base2类构造函数 c=sc; cout“构造 base2 类“endl; ;,33,class derived:public base1, public base2int
45、d;public:derived(int sa, int sb, int sc, int sd): base(sa),base1(sa,sb),base2(sa,sc) d=sd; cout“构造 derived 类“endl; ; int main() derived obj(2,4,6,8);return 0; ,34,运行结果: 构造 base 类 构造 base1 类 构造 base2 类 构造 derived 类,4.5 赋值兼容规则,赋值兼容规则是指在公有派生情况下,派生类的对象可用到基类对象的那些地方。1. 派生类的对象可以赋给基类的对象derived 类是 base 类的派生d
46、ervied d;base b;b=d;2. 派生类的对象可以初始化基类的引用derived d;base,54,例4.17 #include class base public:int i;base(int x)i=x;void show()cout“base“iendl; ; class derived:public base public:derived(int x):base(x) ;void show()cout“derived“iendl; ;,void main() base b1(11); b1.show(); derived d1(22); b1=d1; b1.show(); derived d2(33); base ,注意:(1)声明为指向基类对象的指针可以指向它的派生类的对象,但不允许指向它的私有派生类的对象。 (2)允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。 (3)声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类中定义的成员。,