【教学课件】第11章继承与派生.ppt
第第1111章章继承与派生承与派生王雪晶内容11.1继承与派生的概念11.2派生类的声明方式11.3派生类的构成11.4派生类成员的访问属性11.5派生类的构造函数和析构函数11.6多重继承11.7基类与派生类的转换11.8继承与组合面向对象程序设计的特点n抽象n封装n继承n多态性classEmployeestringfirst_name,family_name;charmiddle_initial;Datehiring_date;shortdepartment;/.;classManagerEmployeeemp;/managersemployeerecordlistgroup;/peoplemanagedshortlevel;/.;考虑做一个程序,处理某公司所雇佣人员的问题。雇员的数据结构经理的数据结构11.1继承与派生的概念n在C+中可重用性是通过继承机制来实现的。n所谓“继承”就是在一个已存在类的基础上建立一个新的类。n已存在的类称为“基类”或“父类”。n新建立的类称为“派生类”或“子类”。11.1继承与派生的概念n一个派生类只从一个基类派生,这称为单继承。箭头表示继承的方向,从派生类指向基类。11.1继承与派生的概念n一个派生类有两个或多个基类的称为多重继承。关于基类和派生类的关系:n派生类是基类的具体化n基类则是派生类的抽象基类Student,通过单继承建立派生类Student1:classStudent1:publicStudent/声明基类是Studentpublic:voiddisplay_1()/新增加的成员函数coutage:ageendl;coutaddress:addrnumnamesex;voiddisplay()coutnum:numendl;coutname:nameendl;coutsex:sexendl;private:/基类私有成员intnum;stringname;charsex;公用继承classStudent1:publicStudentpublic:voiddisplay_1()coutnum:numendl;/错误coutname:nameendl;/错误coutsex:sexendl;/错误coutage:ageendl;coutaddress:addrendl;private:intage;stringaddr;可以将派生类Student1的声明改为classStudent1:publicStudentpublic:voiddisplay_1()coutage:ageendl;/正确coutaddress:addrendl;/正确private:intage;stringaddr;intmain()Student1stud;stud.display();stud.display_1();return0;私有继承classStudent1:privateStudentpublic:voiddisplay_1()/输出两个数据成员的值coutage:ageendl;/正确coutaddress:addrendl;/正确private:intage;stringaddr;intmain()Student1stud1;stud1.display();/错误stud1.display_1();/正确stud1.age=18;/错误return0;可将上面的私有派生类的成员函数定义改写voiddisplay_1()/输出5个数据成员的值display():coutage:ageendl;coutaddress:addrendl;main函数可改写为intmain()Student1stud1;stud1.display_1();return0;从类的用户角度来看,保护成员等价于私有成员。但有一点与私有成员不同,保护成员可以被派生类的成员函数引用。保护成员和保护继承基类成员在派生类的访问属性基类中的成员公用派生私有派生保护派生私有成员不可访问不可访问不可访问公用成员公用私有保护保护成员保护私有保护例11.3在派生类中引用保护成员。#include#includeusingnamespacestd;classStudent/声明基类public:/基类公用成员voiddisplay();protected:/基类保护成员intnum;stringname;charsex;voidStudent:display()/定义基类成员函数coutnum:numendl;coutname:nameendl;coutsex:sexendl;classStudent1:protectedStudentpublic:voiddisplay1();/派生类公用成员函数private:intage;/派生类私有数据成员stringaddr;/派生类私有数据成员;voidStudent1:display1()/定义派生类公用成员函数coutnum:numendl;/合法coutname:nameendl;/合法coutsex:sexendl;/合法coutage:ageendl;/合法coutaddress:addrendl;/合法intmain()Student1stud1;stud1.display1();/合法stud1.num=10023;/错误return0;多级派生时的访问属性n类B称为类A的直接派生类,类C称为类A的间接派生类。n类A是类B的直接基类,是类C的间接基类。例:多级派生的访问属性。classA/基类public:inti;protected:voidf2();intj;private:intk;classB:publicA/public方式public:voidf3();protected:voidf4();private:intm;classC:protectedB/protected方式public:voidf5();private:intn;各成员在不同类中的访问属性如下:if2jk f3f4 m f5 n基类基类A公公用用 保保护护 保保护护 私私有有公公用用派派生类生类B 公公用用 保保护护 保保护护 不不可可访问访问 公公用用 保保护护 私私有有 保保护护派派生类生类C 保保护护 保保护护 保保护护 不不可可访问访问 保保护护 保保护护 不不可可访问访问 公公用用 私私有有11.5派生类的构造函数和析构函数n设计派生类的构造函数n派生类所增加的数据成员的初始化n基类的数据成员初始化n思路:n在执行派生类的构造函数时,调用基类的构造函数。#include#includeusingnamespacestd;classStudentpublic:Student(intn,stringnam,chars)/基类构造函数num=n;name=nam;sex=s;Student()/基类析构函数protected:/保护部分intnum;stringname;charsex;11.5.1简单的派生类的构造函数classStudent1:publicStudent/声明派生类Student1public:/派生类的公用部分Student1(intn,stringnam,chars,inta,stringad):Student(n,nam,s)age=a;addr=ad;voidshow()coutnum:numendl;coutname:nameendl;coutsex:sexendl;coutage:ageendl;coutaddress:addrendlendl;Student1()/派生类析构函数private:/派生类的私有部分intage;stringaddr;intmain()Student1stud1(10010,Wang-li,f,19,115BeijingRoad,Shanghai);Student1stud2(10011,Zhang-fun,m,21,213ShanghaiRoad,Beijing);stud1.show();/输出第一个学生的数据stud2.show();/输出第二个学生的数据return0;n其一般形式为派生类构造函数名(总参数表列):基类构造函数名(参数表列)派生类中新增数据成员初始化语句11.5.1简单的派生类的构造函数将派生类构造函数在类外面定义n类体中的函数声明:Student1(intn,stringnam,chars,inta,stringad);n类外的派生类构造函数定义:Student1Student1(intn,stringnam,chars,inta,stringad):Student(n,nam,s)age=a;addr=ad;11.5.1简单的派生类的构造函数n执行构造函数的顺序是:1.派生类构造函数先调用基类构造函数;2.再执行派生类构造函数本身(即派生类构造函数的函数体)。n在派生类对象释放时1.先执行派生类析构函数Student1()2.再执行其基类析构函数Student()。Manager:Manager(conststring&n,intd,intlvl):family_name(n),/error:family_namenotdeclaredinmanagerdepartment(d),/error:departmentnotdeclaredinmanagerlevel(lvl)/.派生类的构造函数只能描述它自己的成员和自己的直接基类的初始式,它不能直接去初始化基类的成员。11.5.1简单的派生类的构造函数11.5.2有子对象的派生类的构造函数n类的数据成员中可以包含类对象,如:Students1;ns1就是类对象中的内嵌对象,称为子对象,即对象中的对象。例11.6包含子对象的派生类的构造函数。#include#includeusingnamespacestd;classStudentpublic:/公用部分Student(intn,stringnam)/基类构造函数num=n;name=nam;voiddisplay()coutnum:numendlname:nameendl;protected:/保护部分intnum;stringname;classStudent1:publicStudent/声明公用派生类Student1public:Student1(intn,stringnam,intn1,stringnam1,inta,stringad):Student(n,nam),monitor(n1,nam1)/派生类构造函数age=a;addr=ad;voidshow();voidshow_monitor();/成员函数,输出子对象private:/派生类的私有数据Studentmonitor;/定义子对象(班长)intage;stringaddr;voidStudent1:show()coutThisstudentis:endl;display();/输出num和namecoutage:ageendl;/输出agecoutaddress:addrendlendl;/输出addrvoidStudent1:show_monitor()coutendlClassmonitoris:endl;monitor.display();/调用基类成员函数intmain()Student1stud1(10010,Wang-li,10001,Li-sun,19,115BeijingRoad,Shanghai);stud1.show();/输出学生的数据stud1.show_monitor();/输出子对象的数据return0;11.5.2有子对象的派生类的构造函数派生类构造函数的任务应该包括3个部分:(1)对基类数据成员初始化;(2)对子对象数据成员初始化;(3)对派生类数据成员初始化。11.5.2有子对象的派生类的构造函数n执行派生类构造函数的顺序是:调用基类构造函数,对基类数据成员初始化;调用子对象构造函数,对子对象数据成员初始化;再执行派生类构造函数本身,对派生类数据成员初始化。11.5.3多层派生时的构造函数一个类不仅可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。例11.7 多级派生情况下派生类的构造函数。#include#includeusingnamespacestd;classStudentpublic:/公用部分Student(intn,stringnam)/基类构造函数num=n;name=nam;voiddisplay()/输出基类数据成员coutnum:numendl;coutname:nameendl;protected:/保护部分intnum;/基类有两个数据成员stringname;classStudent1:publicStudentpublic:Student1(intn,charnam10,inta):Student(n,nam)age=a;voidshow()/输出num,name和agedisplay();/输出num和namecoutage:ageendl;private:/派生类的私有数据intage;/增加一个数据成员;classStudent2:publicStudent1public:Student2(intn,stringnam,inta,ints):Student1(n,nam,a)score=s;voidshow_all()/输出全部数据成员show();/输出num和namecoutscore:scoreendl;/输出ageprivate:intscore;/增加一个数据成员;intmain()Student2stud(10010,Li,17,89);stud.show_all();/输出学生的全部数据return0;派生关系如图所示11.5.4派生类构造函数的特殊形式1.当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空。2.如果在基类中构造函数没有参数,那么在定义派生类构造函数时可不写基类构造函数。3.如果在基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需对派生类自己的数据成员初始化,则可以不必显式地定义派生类构造函数。11.5.4派生类构造函数的特殊形式4.如果在基类或子对象类型的声明中定义了带参数的构造函数,那么就必须显式地定义派生类构造函数,并在派生类构造函数中写出基类或子对象类型的构造函数及其参数表。5.如果在基类中既定义无参的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数。11.5.5派生类的析构函数n派生类析构函数对派生类中所增加的成员进行清理工作。n在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。n调用的顺序与构造函数正好相反:n先执行派生类自己的析构函数n然后调用子对象的析构函数,对子对象进行清理n最后调用基类的析构函数,对基类进行清理。11.6多重继承n一个派生类有两个或多个基类,派生类从两个或多个基类中继承所需的属性。这种行为称为多重继承。classD:publicA,privateB,protectedC类D新增加的成员11.6.2多重继承派生类的构造函数n派生类构造函数名(总参数表列):n基类1构造函数(参数表列)n基类2构造函数(参数表列)n基类3构造函数(参数表列)n派生类中新增数成员据成员初始化语句n执行顺序例11.8声明一个教师(Teacher)类和一个学生(Student)类,用多重继承的方式声明一个研究生(Graduate)派生类。教师类中包括数据成员name(姓名)、age(年龄)、title(职称)。学生类中包括数据成员name1(姓名)、age(性别)、score(成绩)。在定义派生类对象时给出初始化的数据,然后输出这些数据。#include#includeusingnamespacestd;classTeacher/声明类Teacher(教师)public:/公用部分Teacher(stringnam,inta,stringt)/构造函数name=nam;age=a;title=t;voiddisplay()/输出教师有关数据coutname:nameendl;coutageageendl;couttitle:titleendl;protected:/保护部分stringname;intage;stringtitle;/职称;classStudent/定义类Student(学生)public:Student(charnam,chars,floatsco)strcpy(name1,nam);sex=s;score=sco;/构造函数voiddisplay1()/输出学生有关数据coutname:name1endl;coutsex:sexendl;coutscore:scoreendl;protected:/保护部分stringname1;charsex;floatscore;/成绩;classGraduate:publicTeacher,publicStudentpublic:Graduate(stringnam,inta,chars,stringt,floatsco,floatw):Teacher(nam,a,t),Student(nam,s,sco),wage(w)voidshow()coutname:nameendl;coutage:ageendl;coutsex:sexendl;coutscore:scoreendl;couttitle:titleendl;coutwages:wageendl;private:floatwage;/工资;intmain()Graduategrad1(Wang-li,24,f,assistant,89.5,1234.5);grad1.show();return0;程序运行结果如下:name:Wang-liage:24sex:fscore:89.5title:assistancewages:1234.511.6.3多重继承引起的二义性问题n二义性问题:由于继承的成员同名而产生的。n分别讨论下列3种情况:(1)两个基类有同名成员。classApublic:inta;voiddisplay();classBpublic:inta;voiddisplay();classC:publicA,publicBpublic:intb;voidshow();C c1;c1.a=3;c1.display();可以用基类名来限定:c1.A:a=3;c1.A:display();(2)两个基类和派生类三者都有同名成员。classC:publicA,publicBpublic:inta;voiddisplay();C c1;c1.a=3;c1.display();要在派生类外访问基类A中的成员,应指明作用域Ac1.A:a=3;c1.A:display();(3)如果类A和类B是从同一个基类派生classB:publicNpublic:inta2;classC:publicA,publicBpublic:inta3;voidshow()couta3=a3endl;classNpublic:inta;voiddisplay()coutA:a=”aendl;classA:publicNpublic:inta1;派生类C中成员怎样访问类A中从基类N继承下来的成员?c1.a=3;c1.display();或c1.N:a=3;c1.N:display();/error通过类N的直接派生类名来指出:c1.A:a=3;c1.A:display();11.6.4虚基类1.虚基类的作用使得在继承间接共同基类时只保留一份成员。将类A声明为虚基类,方法如下:classA/声明基类A;classB:virtualpublicA;classC:virtualpublicA;n声明虚基类的一般形式为class派生类名:virtual继承方式基类名D类int data;int data_b;int data_cvoid fun();int data_d;void fun_d();A类int data;void fun();B类int data;void fun();int data_b;C类int data;void fun();int data_c;virtualvirtual2.虚基类的初始化如果虚基类只有带参数的构造函数,则在其所有派生类通过构造函数的初始化表对虚基类进行初始化。classA/定义基类AA(inti)/基类构造函数,有一个参数;classB:virtualpublicA/A作为B的虚基类B(intn):A(n);classC:virtualpublicA/A作为C的虚基类C(intn):A(n);classD:publicB,publicCD(intn):A(n),B(n),C(n);3.虚基类的简单应用举例#include#includeusingnamespacestd;classPersonpublic:Person(stringnam,chars,inta)/构造函数name=nam;sex=s;age=a;protected:/保护成员stringname;charsex;intage;class Teacher:virtual public Person public:Teacher(string nam,char s,int a,string t):Person(nam,s,a)title=t;protected:string title;/职称;class Student:virtual public Personpublic:Student(string nam,char s,int a,float sco):Person(nam,s,a)score=sco;protected:float score;/成绩;classGraduate:publicTeacher,publicStudentpublic:Graduate(stringnam,chars,inta,stringt,floatsco,floatw):Person(nam,s,a),Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w)voidshow()/输出研究生的有关数据coutname:nameendl;coutage:ageendl;coutsex:sexendl;coutscore:scoreendl;couttitle:titleendl;coutwages:wageendl;private:floatwage;/工资;11.7基类与派生类的转换(1)派生类对象可以向基类对象赋值。Aa1;/定义基类A对象a1Bb1;/定义类A的公用派生类B的对象b1a1=b1;/用派生类B对象b1对基类对象a1赋值11.7基类与派生类的转换(2)派生类对象可以向基类对象的引用进行赋值或初始化。Aa1;Bb1;A&r=a1;r=b1;A&r=b1;11.7基类与派生类的转换(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。如:voidfun(A&r)coutr.numendl;fun(b1);11.7基类与派生类的转换(4)派生类对象的地址可以赋给指向基类对象的指针变量。A*pa;Bb1;pa=&b1;例11.10定义一个基类Student(学生),再定义Student类的公用派生类Graduate(研究生),用指向基类对象的指针输出数据。#include#includeusingnamespacestd;classStudent/声明Student类public:Student(int,string,float);/声明构造函数voiddisplay();/声明输出函数private:intnum;stringname;floatscore;Student:Student(intn,stringnam,floats)/定义构造函数num=n;name=nam;score=s;voidStudent:display()/定义输出函数coutendlnum:numendl;coutname:nameendl;coutscore:scoreendl;classGraduate:publicStudentpublic:Graduate(int,string,float,float);/声明构造函数voiddisplay();/声明输出函数private:floatpay;/工资;Graduate:Graduate(intn,stringnam,floats,floatp):Student(n,nam,s),pay(p)voidGraduate:display()/定义输出函数Student:display();/调用Student类的display函数coutpay=paydisplay();pt=&grad1;/指针指向grad1pt-display();11.8继承与组合n在一个类中以另一个类的对象作为数据成员的,称为类的组合。classTeacher/教师类public:private:intnum;stringname;charsex;classBirthDatepublic:private:intyear;intmonth;intday;class Professor:public Teacherpublic:private:BirthDate birthday;继承是纵向的,组合是横向的voidfun1(Teacher&);voidfun2(BirthDate&);在main函数中调用这两个函数:fun1(prof1);/正确fun2(prof1.birthday);/正确fun2(prof1);/错误