《第12章组合与继承》由会员分享,可在线阅读,更多相关《第12章组合与继承(114页珍藏版)》请在金锄头文库上搜索。
1、程序设计cs.sjtu2011.9程序设计-1第第12章章 组合与继承组合与继承v组合合v继承承v虚函数与多虚函数与多态性性v纯虚函数与抽象虚函数与抽象类v多多继承承v面向面向对象象设计范例范例程序设计cs.sjtu2011.9程序设计-2派生类的概念v继承是面向承是面向对象程序象程序设计的一个重要特征,它的一个重要特征,它允允许在已有在已有类的基的基础上上创建新的建新的类v基基类、父、父类v派生派生类、导出出类或子或子类v继承可以承可以让程序程序员在已有在已有类的基的基础上通上通过增加增加或修改少量代或修改少量代码的方法得到新的的方法得到新的类,从而,从而较好好地解决代地解决代码重用的重用的
2、问题。程序设计cs.sjtu2011.9程序设计-3派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设计-4派生类的定义v一般格式:一般格式:class派生派生类名:派生方法名:派生方法基基类名名/派生派生类新增的数据成新增的数据成员和成和成员函数函数;v派生方法:派生方法:1.公有派生公有派生:public2.私有派生:私有派生:private3.保保护派生:派生:
3、protected程序设计cs.sjtu2011.9程序设计-5派生实例派生实例classbaseintx;public:voidsetx(intk);classderived1:publicbaseinty;public:voidsety(intk);Derived1有两个数据有两个数据成员:成员:x,y。有两个成员函数:有两个成员函数:setx和和sety程序设计cs.sjtu2011.9程序设计-6派生类对基类成员的访问派生类对基类成员的访问v派生派生类的成的成员函数不能函数不能访问基基类的私有数的私有数据成据成员vprotected访问特性特性protected成成员是一是一类特殊的私
4、有成特殊的私有成员,它不,它不可以被全局函数或其他可以被全局函数或其他类的成的成员函数函数访问,但能被派生但能被派生类的成的成员函数函数访问protected成成员破坏了破坏了类的封装,基的封装,基类的的protected成成员改改变时,所有派生,所有派生类都要修改都要修改程序设计cs.sjtu2011.9程序设计-7派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设
5、计-8基类成员的基类成员的访问说明符访问说明符继承类型继承类型public继承继承protected继承继承private继承继承public在派生类中为在派生类中为public在派生类中为在派生类中为protected在派生类中为在派生类中为private可以由任何非可以由任何非static可以直接由任何非可以直接由任何非static可以直接由任何非可以直接由任何非static成员函数、友元函数和成员函数、友元函数和成员函数、友元函数成员函数、友元函数成员函数、友元函数成员函数、友元函数非成员函数访问非成员函数访问访问访问访问访问protecetd在派生类中为在派生类中为proteced在派
6、生类中为在派生类中为protected在派生类中在派生类中private可以直接由任何非可以直接由任何非static成员函数、友元函数访问成员函数、友元函数访问private在派生类中隐藏在派生类中隐藏在派生类中隐藏在派生类中隐藏在派生类中隐藏在派生类中隐藏可以通过基类的可以通过基类的public或或protected成员函数或非成员函数或非static成员函数和友元函数访问成员函数和友元函数访问派生类对基类成员的访问性派生类对基类成员的访问性 程序设计cs.sjtu2011.9程序设计-9Derived1不可访问不可访问 IntxprivateIntypublicSetx()Sety()cl
7、assbaseintx;public:voidsetx(intk);classderived1:publicbaseinty;public:voidsety(intk);程序设计cs.sjtu2011.9程序设计-10继承实例继承实例v定定义一个二一个二维平面上的点平面上的点类型,可以型,可以设置点的位置,置点的位置,获取点的位置。在此基取点的位置。在此基础上,上,扩展出一个三展出一个三维空空间上的点上的点类型。型。程序设计cs.sjtu2011.9程序设计-11point_2d的定义的定义classpoint_2dprivate:intx,y;public:voidsetpoint2(int
8、a,intb)x=a;y=b;intgetx()returnx;intgety()returny;程序设计cs.sjtu2011.9程序设计-12point_3d的定义的定义classpoint_3d:publicpoint_2dintz;public:voidsetpoint3(inta,intb,intc)setpoint2(a,b);z=c;intgetz()returnz;程序设计cs.sjtu2011.9程序设计-13point_3d的讨论的讨论vpoint_3d的的组成:有三个数据成成:有三个数据成员:x,y和和z,有,有五个公有的成五个公有的成员函数:函数:setpoint2,s
9、etpoint3,getx,gety和和getz。vpoint_3d的成的成员函数无法直接函数无法直接访问基基类的的x和和y,因此在因此在setpoint3函数中必函数中必须调用在用在point_2d的公的公有成有成员函数函数setpoint2实现。vpoint_3d类的使用和普通的使用和普通类完全一完全一样,用,用户不用不用去管去管这个个类是用是用继承方式从另外一个承方式从另外一个类扩展而来,展而来,还是完全直接定是完全直接定义的。的。程序设计cs.sjtu2011.9程序设计-14Point_3d的使用的使用intmain()point_2dp1;point_3dp2;p1.setpoin
10、t2(1,2);coutp1:(p1.getx(),p1.gety()endl;p2.setpoint3(1,2,3);coutp2:(p2.getx(),p2.gety(),p2.getz()endl;return0;P1:(1,2)P2:(1,2,3)程序设计cs.sjtu2011.9程序设计-15派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设计-16派生类的
11、构造函数和析构函数派生类的构造函数和析构函数 v由于派生由于派生类继承了其基承了其基类的成的成员,所以在,所以在建立派生建立派生类的的实例例对象象时,必,必须初始化基初始化基类继承的数据成承的数据成员。派生。派生类对象析构象析构时,也必也必须析构基析构基类对象。象。v派生派生类不不继承基承基类的构造函数、析构函数的构造函数、析构函数和和赋值运算符,但是派生运算符,但是派生类的构造函数、的构造函数、析构函数和析构函数和赋值运算符能运算符能调用基用基类的构造的构造函数、析构函数和函数、析构函数和赋值运算符。运算符。程序设计cs.sjtu2011.9程序设计-17派生类的构造函数派生类的构造函数v基
12、基类成成员的初始化由基的初始化由基类的构造函数完成。派的构造函数完成。派生生类的构造函数的构造函数调用基用基类的构造函数完成基的构造函数完成基类成成员的初始化。的初始化。v派生派生类构造函数可以构造函数可以隐式式调用基用基类缺省的构造缺省的构造函数,也可以在派生函数,也可以在派生类的构造函数的初始化列的构造函数的初始化列表表显式地式地调用基用基类的构造函数。的构造函数。程序设计cs.sjtu2011.9程序设计-18构造函数的格式构造函数的格式v派生派生类构造函数的格式:构造函数的格式:派生派生类构造函数名(参数表):构造函数名(参数表):基基类构造函数名(参数表)构造函数名(参数表)。v基基
13、类构造函数中的参数表通常来源于派生构造函数中的参数表通常来源于派生类构造函数构造函数的参数表,也可以用常数的参数表,也可以用常数值。v如果构造派生如果构造派生类对象象时调用的是基用的是基类的缺省构造函数,的缺省构造函数,则可以不要初始化列表。可以不要初始化列表。v如果派生如果派生类新增的数据成新增的数据成员中含有中含有对象成象成员,则在在创建建对象象时,先,先执行基行基类的构造函数,再的构造函数,再执行成行成员对象象的构造函数,最后的构造函数,最后执行自己的构造函数体。行自己的构造函数体。程序设计cs.sjtu2011.9程序设计-19派生类对象的析构派生类对象的析构v派生派生类的析构函数的析
14、构函数值析构自己新增的数析构自己新增的数据成据成员,基,基类成成员的析构由基的析构由基类的析构的析构函数析构函数析构v派生派生类析构函数会自析构函数会自动调用基用基类的析构的析构函数函数v派生派生类对象析构象析构时,先,先执行派生行派生类的析的析构函数,再构函数,再执行基行基类的析构函数的析构函数程序设计cs.sjtu2011.9程序设计-20派生类构造实例派生类构造实例v定定义一个二一个二维平面上的点平面上的点类,并从它派,并从它派生出一个生出一个圆类程序设计cs.sjtu2011.9程序设计-21point2.h/DefinitionofclassPoint#ifndefPOINT2_H#
15、definePOINT2_HclassPointpublic:Point(int=0,int=0);/defaultconstructorPoint();/destructorprotected:/accessiblebyderivedclassesintx,y;/xandycoordinatesofPoint;#endif程序设计cs.sjtu2011.9程序设计-22point2.cpp#includepoint2.h/ConstructorforclassPointPoint:Point(inta,intb)x=a;y=b;coutPointconstructor:x,yendl;/De
16、structorforclassPointPoint:Point()coutPointdestructor:x,yendl;程序设计cs.sjtu2011.9程序设计-23circle2.h#ifndefCIRCLE2_H#defineCIRCLE2_H#includepoint2.hclassCircle:publicPointpublic:/defaultconstructorCircle(doubler=0.0,intx=0,inty=0);Circle();private:doubleradius;#endif程序设计cs.sjtu2011.9程序设计-24circle2.cpp#in
17、cludecircle2.h/ConstructorforCirclecallsconstructorforPointCircle:Circle(doubler,inta,intb):Point(a,b)/callbase-classConstructorradius=r;/shouldvalidatecoutCircleconstructor:radiusisradiusx,yendl;/DestructorroiclassCircleCircle:Circle()coutCircledestructor:radiusisradiusx,yendl;程序设计cs.sjtu2011.9程序设计
18、-25Circle类的应用类的应用#includepoint2.h“#includecircle2.hintmain()/ShowconstructoranddestructorcallsforPointPointp(11,22);coutendl;Circlecircle1(4.5,72,29);coutendl;Circlecircle2(10,5,5);coutendl;return0;Pointconstructor:11,22Pointdestructor:11,22Pointconstructor:72,29Circleconstructor:radiusis4.572,29Poi
19、ntconstructor:5,5Circleconstructor:radiusis105,5Circledestructor:radiusis105,5Pointdestructor:5,5Circledestructor:radiusis4.572,29Pointdestructor:72,29程序设计cs.sjtu2011.9程序设计-26派生类构造函数的构造规则v若基若基类使用缺省或不使用缺省或不带参数的构造函数,参数的构造函数,则在在派生派生类定定义构造函数是可略去:基构造函数是可略去:基类构造函数构造函数名(参数表)。此名(参数表)。此时若派生若派生类也不需要构造函也不需要构造函
20、数,数,则可不定可不定义构造函数。构造函数。v当基当基类构造函数需要参数,而派生构造函数需要参数,而派生类本身并不本身并不需要构造函数需要构造函数时,派生,派生类还必必须定定义构造函数。构造函数。该函数只是起了一个参数函数只是起了一个参数传递作用。作用。v如果省略了派生如果省略了派生类的构造函数,那么就由派生的构造函数,那么就由派生类的默的默认构造函数构造函数调用基用基类的默的默认构造函数。构造函数。程序设计cs.sjtu2011.9程序设计-27派生类实例派生类实例v定定义一个一个图书馆系系统中的中的读者者类,每个,每个读者的信息包括:卡号、姓名、者的信息包括:卡号、姓名、单位、位、允允许借
21、借书的数量以及已借的数量以及已借书记录。学生。学生最多允最多允许借借5本本书,教,教师最多允最多允许借借10本本书。程序设计cs.sjtu2011.9程序设计-28设计过程设计过程v系系统中有两中有两类读者:学生者:学生读者和教者和教师读者。者。v这两两类读者有一部分内容是相同的:卡号、者有一部分内容是相同的:卡号、姓名和姓名和单位。位。v可将两可将两类读者的共同部分内容者的共同部分内容设计成一个成一个基基类。v学生学生读者和教者和教师读者从基者从基类派生,增加已派生,增加已借借书的数量以及已借的数量以及已借书记录两个数据成两个数据成员,并将允并将允许借借书的数量定的数量定义为整个整个类共享的
22、共享的常量常量程序设计cs.sjtu2011.9程序设计-29基类的设计基类的设计classreaderintno;charname10;chardept20;public:reader(intn,char*nm,char*d)no=n;strcpy(name,nm);strcpy(dept,d);程序设计cs.sjtu2011.9程序设计-30教师读者类的设计教师读者类的设计classreaderTeacher:publicreaderenumMAX=10;intborrowed;intrecordMAX;public:readerTeacher(intn,char*nm,char*d):r
23、eader(n,nm,d)borrowed=0;boolbookBorrow(intbookNo);boolbookReturn(intbookNo);voidshow();/显示已借示已借书信息信息;程序设计cs.sjtu2011.9程序设计-31学生读者类的设计学生读者类的设计classreaderStudent:publicreaderenumMAX=5;intborrowed;intrecordMAX;public:readerStudent(intn,char*nm,char*d):reader(n,nm,d)borrowed=0;boolbookBorrow(intbookNo);
24、boolbookReturn(intbookNo);voidshow();/显示已借示已借书信息信息;程序设计cs.sjtu2011.9程序设计-32派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设计-33重定义基类的函数重定义基类的函数 v派生派生类是基是基类的的扩展,可以是保存的数据内容的展,可以是保存的数据内容的扩展,也可以是功能的展,也可以是功能的扩展。展。
25、v当派生当派生类对基基类的某个功能的某个功能进行行扩展展时,他定,他定义的成的成员函数名可能会和基函数名可能会和基类的成的成员函数名重复。函数名重复。v如果只是函数名相同,而原型不同如果只是函数名相同,而原型不同时,系,系统认为派生派生类中有两个重中有两个重载函数。如果原型完全相同,函数。如果原型完全相同,则派生派生类的函数会覆盖基的函数会覆盖基类的函数。的函数。这称称为重定重定义基基类的成的成员函数。函数。程序设计cs.sjtu2011.9程序设计-34实例实例v定定义一个一个圆类型,用于保存型,用于保存圆以及以及输出出圆的面的面积和周和周长。在此。在此类型的基型的基础上派上派生出一个球生出
26、一个球类型,可以型,可以计算球的表面算球的表面积和体和体积。程序设计cs.sjtu2011.9程序设计-35圆类的设计圆类的设计v数据成数据成员:圆的半径的半径v成成员函数:由于需要提供函数:由于需要提供圆的面的面积和周和周长,需要提供两个公有的成,需要提供两个公有的成员函数。除函数。除了了这些之外,些之外,还需要一个构造函数需要一个构造函数程序设计cs.sjtu2011.9程序设计-36圆类的定义圆类的定义classcircleprotected:doubleradius;public:circle(doubler=0)radius=r;doublegetr()returnradius;do
27、ublearea()return3.14*radius*radius;doublecircum()return2*3.14*radius;程序设计cs.sjtu2011.9程序设计-37球类的定义球类的定义classball:publiccirclepublic:ball(doubler=0):circle(r)doublearea()return4*3.14*radius*radius;doublevolumn()return4*3.14*radius*radius*radius/3;程序设计cs.sjtu2011.9程序设计-38Ball类的构造函数类的构造函数vBall类没有新增加数据成
28、没有新增加数据成员,因而不需,因而不需要构造函数。但基要构造函数。但基类的构造函数需要参的构造函数需要参数,所以数,所以ball类必必须写构造函数写构造函数vBall类构造函数的作用是构造函数的作用是为circle类的构的构造函数造函数传递参数参数程序设计cs.sjtu2011.9程序设计-39Ball类的类的area函数函数vBall类包含了两个原型完全一包含了两个原型完全一样的的area函数。一个是自己定函数。一个是自己定义的,一个是从的,一个是从circle类继承来的承来的v当当对ball类的的对象象调用用area函数函数时,调用用的是的是ball类自己定自己定义的的area函数函数程序
29、设计cs.sjtu2011.9程序设计-40派生类引用基类的同名函数派生类引用基类的同名函数v派生派生类中重新定中重新定义基基类的成的成员函数函数时,它的功能往往是基它的功能往往是基类功能的功能的扩展。展。为完完成成扩展的工作,派生展的工作,派生类版本通常要版本通常要调用用基基类中的中的该函数版本。函数版本。v引用基引用基类的同名函数必的同名函数必须使用作用域运使用作用域运算符,否算符,否则会由于派生会由于派生类成成员函数函数实际上上调用了自身而引起无用了自身而引起无穷递归。这样会会使系使系统用光内存,是致命的运行用光内存,是致命的运行时错误。程序设计cs.sjtu2011.9程序设计-41实
30、例实例v在在circle类的基的基础上定上定义一个一个cylinder类。可。可以以计算算圆柱体的表面柱体的表面积和体和体积v设计考考虑:存存储圆柱体可以在柱体可以在圆的基的基础上增加一个高度。上增加一个高度。圆柱体的表面柱体的表面积是上下两个是上下两个圆的面的面积加上它的加上它的侧面面积圆柱体的体柱体的体积是底面是底面积乘上高度。而求乘上高度。而求圆的面的面积的函数在的函数在circle类中已中已经存在。存在。程序设计cs.sjtu2011.9程序设计-42Cylinder类的定义类的定义classcylinder:publiccircledoubleheight;public:cylind
31、er(doubler=0,doubleh=0):circle(r)height=h;doublegeth()returnheight;doublearea()return2*circle:area()+circum()*height;doublevolumn()returncircle:area()*height;程序设计cs.sjtu2011.9程序设计-43Ball和和cylinder类的使用类的使用intmain()circlec(3);ballb(2);cylindercy(1,2);coutcircle:r=c.getr()endl;coutarea=c.area()tcircum=
32、c.circum()endl;coutball:r=b.getr()endl;coutarea=b.area()tvolumn=b.volumn()endl;coutcylinder:r=cy.getr()th=cy.geth()endl;coutarea=cy.area()tvolumn=cy.volumn()endl;return0;程序设计cs.sjtu2011.9程序设计-44执行结果执行结果circle:r=3area=28.26circum=18.84ball:r=2area=50.24volumn=33.4933cylinder:r=1area=18.84volumn=6.28程
33、序设计cs.sjtu2011.9程序设计-45派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设计-46派生类作为基类派生类作为基类v基基类本身可以是一个派生本身可以是一个派生类,如:,如:classbaseclassd1:publicbaseclassd2:publicd1v每个派生每个派生类继承他的直接基承他的直接基类的所有成的所有成员。v如果派生如果派生类的基的
34、基类是一个派生是一个派生类,则每个派生每个派生类只只负责他的直接基他的直接基类的构造,依次上溯。的构造,依次上溯。v当构造当构造d2类的的对象象时,会先,会先调用用d1的构造函数,而的构造函数,而d1的构造函数的构造函数执行行时又会先又会先调用用base的构造函数。因此,的构造函数。因此,构造构造d2类的的对象象时,最先初始化的是,最先初始化的是base的数据成的数据成员,再初始化再初始化d1新增的成新增的成员,最后初始化,最后初始化d2新增的成新增的成员。v析构的析构的过程正好相反。程正好相反。程序设计cs.sjtu2011.9程序设计-47实例实例#includeusingnamespac
35、estd;classbaseintx;public:base(intxx)x=xx;coutconstructingbasen;base()coutdestructintbasen;classderive1:publicbaseinty;public:derive1(intxx,intyy):base(xx)y=yy;coutconstructingderive1n;derive1()coutdestructingderive1n;程序设计cs.sjtu2011.9程序设计-48classderive2:publicderive1 intz;public:derive2(intxx,intyy
36、,intzz):derive1(xx,yy)z=zz;coutconstructingderive2n;derive2()coutdestructingderive2n;main()derive2op(1,2,3);return0;constructing baseconstructing derive1constructing derive2destructing derive2destructing derive1destructint base程序设计cs.sjtu2011.9程序设计-49派生类派生类v单继承的格式承的格式v基基类成成员在派生在派生类中的中的访问特性特性v派生派生类对象
37、的构造、析构与象的构造、析构与赋值操作操作v重定重定义基基类的函数的函数v派生派生类作作为基基类v将派生将派生类对象象隐式式转换为基基类对象象程序设计cs.sjtu2011.9程序设计-50将派生类对象隐式转换为基类对象将派生类对象隐式转换为基类对象v将派生将派生类对象象赋给基基类对象象v基基类指指针指向派生指向派生类对象象v基基类的的对象引用派生象引用派生类的的对象象程序设计cs.sjtu2011.9程序设计-51将派生类对象赋给基类对象将派生类对象赋给基类对象v派生派生类中的基中的基类部分部分赋给此基此基类对象,派生象,派生类新增加的成新增加的成员就舍弃了。就舍弃了。赋值后,基后,基类对象
38、和象和派生派生类对象再无任何关系。象再无任何关系。class base public: int a; ;class d1:public base public: int b; ; d1 d;d.a = 1; d.b = 2;base bb = d;cout bb.a; /输出输出1bb.a = 3;cout d.a; /输出输出1程序设计cs.sjtu2011.9程序设计-52基类指针指向派生类对象基类指针指向派生类对象v尽管尽管该指指针指向的指向的对象是一个派生象是一个派生类对象,但由象,但由于它本身是一个基于它本身是一个基类的指的指针,它只能解,它只能解释基基类的的成成员,而不能解,而不能
39、解释派生派生类新增的成新增的成员。因此,只。因此,只能能访问派生派生类中的基中的基类部分。部分。v通通过指指针修改基修改基类对象象时,派生,派生类对象也被修改。象也被修改。d1 d;d.a = 1; d.b = 2;base *bp = &d;cout a; /输出输出1Bp-a = 3;cout d.a; /输出输出3程序设计cs.sjtu2011.9程序设计-53基类的对象引用派生类的对象基类的对象引用派生类的对象v给派生派生类中的基中的基类部分取个部分取个别名。名。v基基类对象改象改变时,派生,派生类对象也被修改。象也被修改。d1 d;d.a = 1; d.b = 2;base &bb
40、= d;cout bb.a; /输出输出1bb.a = 3;cout d.a; /输出输出3程序设计cs.sjtu2011.9程序设计-54classShapepublic:voidprintShapeName()cout“Shape”endl;classPoint:publicShapepublic:voidprintShapeName()cout“Point”endl;classCircle:publicPointpublic:voidprintShapeName()cout“Circle”endl;classCylinder:publicCirclepublic:voidprintSha
41、peName()cout“Cylinder”endl;实例程序设计cs.sjtu2011.9程序设计-55将派生类对象赋给基类对象将派生类对象赋给基类对象inti;PointaPoint;CircleaCircle;CylinderaCylinder;Shapeshapes3=aPoint,aCircle,aCylinder;for(i=0;i3;i+)shapesi.printShapeName();ShapeShapeShape程序设计cs.sjtu2011.9程序设计-56基类指针指向派生类对象基类指针指向派生类对象inti;PointaPoint;CircleaCircle;Cylin
42、deraCylinder;Shape*pShape3=&aPoint,&aCircle,&aCylinder;for(i=0;iprintShapeName();ShapeShapeShape程序设计cs.sjtu2011.9程序设计-57基类的对象引用派生类的对象基类的对象引用派生类的对象inti;PointaPoint;CircleaCircle;CylinderaCylinder;Shape&shape1=aPoint;shape1.printShapeName();shape程序设计cs.sjtu2011.9程序设计-58注意注意v不能将基不能将基类对象象赋给派生派生类对象,除非在基象
43、,除非在基类中定中定义了向派生了向派生类的的类型型转换函数函数v不能将基不能将基类对象地址象地址赋给指向派生指向派生类对象的指象的指针v也不能将指向基也不能将指向基类对象的指象的指针赋给指向派生指向派生类对象的指象的指针。如果程序。如果程序员能确保能确保这个基个基类指指针指向的是一个派生指向的是一个派生类的的对象,象,则可以用可以用reinterpret_cast类型的型的强制制类型型转换。表示程。表示程序序员知道知道这个个风险程序设计cs.sjtu2011.9程序设计-59第第12章章 组合与继承组合与继承v组合合v继承承v虚函数与多虚函数与多态性性v纯虚函数与抽象虚函数与抽象类v多多继承承
44、v面向面向对象象设计范例范例程序设计cs.sjtu2011.9程序设计-60虚函数与多态性虚函数与多态性v多多态性性v虚函数虚函数v虚析构函数虚析构函数程序设计cs.sjtu2011.9程序设计-61多态性v多多态性:不同性:不同对象收到相同的消息象收到相同的消息时产生不同的生不同的动作。作。v多多态性的作用:便于系性的作用:便于系统功能的功能的扩展展程序设计cs.sjtu2011.9程序设计-62多态性的实现多态性的实现v静静态联编:编译时已决定用哪一个函数已决定用哪一个函数实现某一某一动作。作。v动态联编:直到运行:直到运行时才决定用哪一个才决定用哪一个函数来函数来实现动作作程序设计cs.
45、sjtu2011.9程序设计-63静态联编v函数重函数重载:用同一名字:用同一名字实现访问一一组相相关的函数关的函数v运算符重运算符重载v重重载函数是通函数是通过“名字名字压延延”方法来方法来实现。即在。即在编译时将函数名和参数将函数名和参数结合起合起来来创造一个新的函数名,用新的名字替造一个新的函数名,用新的名字替换原有名字。原有名字。程序设计cs.sjtu2011.9程序设计-64运行时多态性运行时多态性v运行运行时多多态性是指必性是指必须等到等到程序程序动态运行运行时才可确定的多才可确定的多态性,性,主要通主要通过继承承结合合动态绑定定获得。得。这与与类的的继承密切相关。承密切相关。因因
46、为存在存在类型的兼容性,所以有些函数只型的兼容性,所以有些函数只有在运行有在运行时才能确定是才能确定是调用父用父类的的还是子是子类的函数。在的函数。在C+C+中,使用中,使用虚函数虚函数(Virtual FunctionsVirtual Functions)来)来实现。程序设计cs.sjtu2011.9程序设计-65虚函数与多态性虚函数与多态性v多多态性性v虚函数虚函数v虚析构函数虚析构函数程序设计cs.sjtu2011.9程序设计-66虚函数v虚函数提供虚函数提供动态重重载方式,允方式,允许函数函数调用与函数用与函数体之体之间的的联系在运行系在运行时才建立。才建立。v虚函数的定虚函数的定义:
47、在基:在基类中用关中用关键词virtual说明,并明,并在派生在派生类中重新定中重新定义的函数称的函数称为虚函数。在派生虚函数。在派生类中重新定中重新定义时,其函数原型,包括返回,其函数原型,包括返回类型、型、函数名、参数个数与参数函数名、参数个数与参数类型的型的顺序都必序都必须与基与基类中的原型完全相同。中的原型完全相同。v当把一个函数定当把一个函数定义为虚函数虚函数时,等于告,等于告诉编译器,器,这个成个成员函数在派生函数在派生类中可能有不同的中可能有不同的实现。必。必须在在执行行时根据根据传递的参数来决定的参数来决定调用哪一个函用哪一个函数数程序设计cs.sjtu2011.9程序设计-6
48、7虚函数的使用虚函数的使用v虚函数是与基虚函数是与基类指指针指向派生指向派生类对象,或基象,或基类对象引用派生象引用派生类对象象结合起来合起来实现多多态性。性。v当基当基类指指针指向派生指向派生类对象或基象或基类对象引用派象引用派生生类对象象时,对基基类指指针或或对象象调用基用基类的虚的虚函数,系函数,系统会到相会到相应的派生的派生类中中寻找此虚函数找此虚函数的重定的重定义。如找到,。如找到,则执行派生行派生类中的函数。中的函数。如没有找到,如没有找到,则执行基行基类的虚函数。的虚函数。程序设计cs.sjtu2011.9程序设计-68虚函数classShapepublic:virtualvoi
49、dprintShapeName()cout“Shape”endl;classPoint:publicShapepublic:virtualvoidprintShapeName()cout“Point”endl;classCircle:publicPointpublic:virtualvoidprintShapeName()cout“Circle”endl;classCylinder:publicCirclepublic:virtualvoidprintShapeName()cout“Cylinder”endl;程序设计cs.sjtu2011.9程序设计-69将派生类对象赋给基类对象将派生类对象
50、赋给基类对象inti;PointaPoint;CircleaCircle;CylinderaCylinder;Shapeshapes3=aPoint,aCircle,aCylinder;for(i=0;i3;i+)shapesi.printShapeName();ShapeShapeShape程序设计cs.sjtu2011.9程序设计-70基类指针指向派生类对象基类指针指向派生类对象inti;PointaPoint;CircleaCircle;CylinderaCylinder;Shape*pShape3=&aPoint,&aCircle,&aCylinder;for(i=0;iprintSh
51、apeName();PointCircleCylinder程序设计cs.sjtu2011.9程序设计-71基类的对象引用派生类的对象基类的对象引用派生类的对象inti;PointaPoint;CircleaCircle;CylinderaCylinder;/Shape*pShape3=&aPoint,&aCircle,&aCylinder;Shape&shape1=aPoint;/for(i=0;iprintShapeName();shape1.printShapeName();Point程序设计cs.sjtu2011.9程序设计-72使用虚函数的注意事项使用虚函数的注意事项 v在派生在派生类
52、中重新定中重新定义虚函数虚函数时,它的原型必,它的原型必须与基与基类中的虚函数完全相同。否中的虚函数完全相同。否则编译器会把器会把它它认为是重是重载函数,而不是虚函数的重定函数,而不是虚函数的重定义。v派生派生类在在对基基类的虚函数重定的虚函数重定义时,关,关键字字virtual可以写也可以不写。不管可以写也可以不写。不管virtual写或者写或者不写,不写,该函数都被函数都被认为是虚函数。但最好是在是虚函数。但最好是在重定重定义时写上写上virtual。程序设计cs.sjtu2011.9程序设计-73例例v正方形是一正方形是一类特殊的矩形,因此,可以特殊的矩形,因此,可以从从rectangl
53、e类派生一个派生一个square类。在。在这两个两个类中,都有一个中,都有一个显示形状的函数。示形状的函数。程序设计cs.sjtu2011.9程序设计-74classrectangleintw,h;public:rectangle(intww,inthh):w(ww),h(hh)virtualvoiddisplay()cout“thisisarectanglen”;classsquare:publicrectanglepublic:square(intss):rectangle(ss,ss)voiddisplay()/虚函数虚函数cout“thisisasquaren”;程序设计cs.sjtu
54、2011.9程序设计-75虚函数与多态性虚函数与多态性v多多态性性v虚函数虚函数v虚析构函数虚析构函数程序设计cs.sjtu2011.9程序设计-76为什么需要虚析构函数为什么需要虚析构函数v构造函数不能是虚函数,但析构函数可以是构造函数不能是虚函数,但析构函数可以是虚函数,而且最好是虚函数虚函数,而且最好是虚函数v如果派生如果派生类新增加的数据成新增加的数据成员中含有指中含有指针,指向指向动态申申请的内存,那么派生的内存,那么派生类必必须定定义析构函数析构函数释放放这部分空部分空间。但如果派生。但如果派生类的的对象是通象是通过基基类的指的指针操作的,操作的,则delete基基类指指针指向的指
55、向的对象就会造成内存泄漏。象就会造成内存泄漏。程序设计cs.sjtu2011.9程序设计-77解决方案解决方案v将基将基类的析构函数定的析构函数定义为虚函数。虚函数。v当析构基当析构基类指向的派生指向的派生类的的对象象时,找,找到基到基类的析构函数。由于基的析构函数。由于基类的析构函的析构函数是虚函数,又会找到派生数是虚函数,又会找到派生类的析构函的析构函数,数,执行派生行派生类的析构函数。的析构函数。程序设计cs.sjtu2011.9程序设计-78虚析构函数的继承性虚析构函数的继承性v和其他的虚函数一和其他的虚函数一样,析构函数的虚函,析构函数的虚函数的性数的性质将被将被继承。承。v如果如果
56、继承承层次次树中的根中的根类的析构函数是的析构函数是虚函数的虚函数的话,所有派生,所有派生类的析构函数都的析构函数都将是虚函数。将是虚函数。程序设计cs.sjtu2011.9程序设计-79第第12章章 组合与继承组合与继承v组合合v继承承v虚函数与多虚函数与多态性性v纯虚函数与抽象虚函数与抽象类v多多继承承v面向面向对象象设计范例范例程序设计cs.sjtu2011.9程序设计-80纯虚函数v纯虚函数:是一个在基虚函数:是一个在基类中中说明的虚函明的虚函数,它在数,它在该基基类中没有定中没有定义,但要在它,但要在它的派生的派生类里定里定义自己的版本,或重新自己的版本,或重新说明明为纯虚函数虚函数
57、v纯虚函数的一般形式虚函数的一般形式virtual类型型函数名(参数表)函数名(参数表)=0程序设计cs.sjtu2011.9程序设计-81纯虚函数实例classshapeprotected:doublex,y;public:shape(doublexx,doubleyy)x=xx;y=yy;virtualdoublearea()=0;virtualvoiddisplay()coutThisisashape.Thepositionis(x,y)n;程序设计cs.sjtu2011.9程序设计-82抽象类v抽象抽象类:如果一个:如果一个类中至少有一个中至少有一个纯虚函数,虚函数,则该类被称被称为抽
58、象抽象类v抽象抽象类使用使用说明:明:抽象抽象类只能作只能作为其他其他类的基的基类,不能建立抽象,不能建立抽象类的的对象。象。可以声明指向抽象可以声明指向抽象类的指的指针或引用,此指或引用,此指针可可指向它的派生指向它的派生类,进而而实现多多态性性抽象抽象类不能用作参数不能用作参数类型、函数返回型、函数返回类型或型或显式式转换类型型如果派生如果派生类中中给除了基除了基类所有所有纯虚函数的虚函数的实现,则该派生派生类不再是抽象不再是抽象类,否,否则仍仍为抽象抽象类程序设计cs.sjtu2011.9程序设计-83抽象类的意义v保保证进入入继承承层次的每个次的每个类都具有都具有纯虚虚函数所要求的行函
59、数所要求的行为,这保保证了了围绕这个个继承承层次所建立起来的次所建立起来的软件系件系统能正常能正常运行,避免了运行,避免了这个个继承承层次的用次的用户由于由于偶偶尔的失的失误(忘了(忘了为它所建立的派生它所建立的派生类提供提供继承承层次所要求的行次所要求的行为)而影响系)而影响系统正常运行正常运行程序设计cs.sjtu2011.9程序设计-84抽象类实例v下面程序用于下面程序用于计算各算各类形状的形状的总面面积#includeclassshapepublic:virtualvoidarea()=0;classrectangle:publicshapefloatw,h;public:rectan
60、gle(floatww,floathh)w=ww;h=hh;voidarea()coutnareais:w*h;classcircle:publicshapefloatr;public:circle(floatrr)r=rr;voidarea()coutnareais:area();ptr=&ob2;ptr-area();程序设计cs.sjtu2011.9程序设计-85第第12章章 组合与继承组合与继承v组合合v继承承v虚函数与多虚函数与多态性性v纯虚函数与抽象虚函数与抽象类v多多继承承v面向面向对象象设计范例范例程序设计cs.sjtu2011.9程序设计-86多重继承v一个派生一个派生类有多
61、个基有多个基类时称称为多重多重继承承v多多继承承时,派生,派生类包含所有基包含所有基类的成的成员ABCD程序设计cs.sjtu2011.9程序设计-87多重继承的定义格式class派生派生类名:名:基基类表表新增派生新增派生类的数据成的数据成员和成和成员函数函数;基基类表表为:派生方法:派生方法1基基类名名1,派生派生方法方法2基基类名名2,派生方法,派生方法n基基类名名n程序设计cs.sjtu2011.9程序设计-88多继承的访问特性多继承的访问特性v与与单继承相同承相同v取决于每个基取决于每个基类的派生方法,和成的派生方法,和成员在在基基类中的中的访问特性特性程序设计cs.sjtu2011
62、.9程序设计-89多重继承的访问特性v与与单继承承规则相同,例:相同,例:#includeclassXinta;public:voidsetX(intx)a=x;voidshowX()couta;classYintb;public:voidsetY(intx)b=x;voidshowY()coutb;classZ:publicX,privateYintc;public:voidsetZ(intx,inty)c=x;setY(y);voidshowZ()coutc;main()Zobj;obj.setX(3);obj.showX();obj.setY(4);obj.showY();/非法非法ob
63、j.setZ(5,6);obj.showZ();类z有三个数据成员和六个成员函数程序设计cs.sjtu2011.9程序设计-90多重继承的构造函数和析构函数v构造函数的定构造函数的定义形式形式派生派生类构造函数名(参数表):基构造函数名(参数表):基类1构构造函数名(参数表),基造函数名(参数表),基类2构造函数名构造函数名(参数表),。基(参数表),。基类n构造函数名构造函数名(参数表)(参数表)v执行行顺序:先序:先执行基行基类(按照(按照继承的次承的次序,而不是构造函数的初始化列表的次序,而不是构造函数的初始化列表的次序),再序),再执行派生行派生类。v析构的次序和构造相反析构的次序和构
64、造相反程序设计cs.sjtu2011.9程序设计-91多重继承实例#includeclassAinti;public:A(intii=0)i=ii;coutA.i=iendl;voidshow()coutA:show()i=iendl;classBinti;public:B(intii=0)i=ii;coutB.i=iendl;voidshow()coutB:show()i=iendl;classC:publicA,publicBinti;public:C(inti1=0,inti2=0,inti3=0):A(i1),B(i2)i=i3;coutC.i=iendl;voidshow()cout
65、C:show()i=iendl;voidmain()Cc(1,2,3);c.show();程序设计cs.sjtu2011.9程序设计-92执行结果A.i=1B.i=2C.i=3C:show()i=3程序设计cs.sjtu2011.9程序设计-93多重继承的主要问题多重继承的主要问题v二二义性性多个基多个基类有相同的的成有相同的的成员名名有两个以上的基有两个以上的基类有共同的基有共同的基类程序设计cs.sjtu2011.9程序设计-94二义性v如果多个基如果多个基类中有相同的成中有相同的成员名,名,则派生派生类在在引用引用时就具有二就具有二义性。例:性。例:classApublic:voidf(
66、);classBpublic:voidf();voidg();classC:publicA,publicBpublic:voidg();voidh();如有:如有:Cx;则x.f()有二有二义性。性。程序设计cs.sjtu2011.9程序设计-95x.f()的解决方法v方法一:方法一:C类的成的成员在引用在引用f时说明是明是A的的f还是是B的的f。如:。如:x.A:f();或或x.B:f();其缺陷是其缺陷是对C的用的用户不利。不利。C用用户必必须知知道自己是从哪些基道自己是从哪些基类派生出来的。派生出来的。v方法二:在方法二:在C类声明中指出基声明中指出基类名。如名。如在在C中可声明:中可声
67、明:voidha()A:f();voidhb()B:f();程序设计cs.sjtu2011.9程序设计-96二义性-cont.v如果一个派生如果一个派生类从多个基从多个基类派生,而派生,而这些基些基类又有公共的基又有公共的基类,则对该基基类中中声明的名字声明的名字进行行访问时,可能会,可能会产生二生二义性。性。这个个问题由虚基由虚基类来解决。来解决。程序设计cs.sjtu2011.9程序设计-97二义性-cont.v如:如:ClassBpublic:intb;ClassB1:publicBprivate:intb1;ClassB2:publicBprivate:intb2;ClassC:pub
68、licB1,publicB2public:intf();private:intd;定定义:Cc;下面对下面对b的访问是有二的访问是有二义性的:义性的:c.bc.B:b下面对下面对b的访问是正确的访问是正确的:的:c.B1:bb.B2:b程序设计cs.sjtu2011.9程序设计-98二义性实例classApublic:voidfun()coutA:fun()endl;classB1:publicApublic:voidfun1()coutB1:fun1()endl;classB2:publicApublic:voidfun1()coutB2:fun1()endl;classD:public1,
69、public2;voidmain()Dobj;/obj.fun1();/不可以执行各有不可以执行各有fun1()函数函数)obj.B1:fun1();obj.B2:fun1();/obj.fun();/不可以执行不可以执行/obj.A:fun();/不可以执行:二义性不可以执行:二义性obj.B1:fun();/无二义性:可以执行无二义性:可以执行程序设计cs.sjtu2011.9程序设计-99虚基类v用途:当一个派生用途:当一个派生类是从多个基是从多个基类派生,派生,而而这些基些基类又有一个公共的基又有一个公共的基类。则在在这个派生个派生类中中访问公共基公共基类中的成中的成员时会有二会有二义
70、性性问题。如上例中的。如上例中的B,B1,B2和和C,他,他们的派生关系是:的派生关系是:BBB1B2Cl如果如果B只有一个只有一个拷贝的话,那么拷贝的话,那么在在C中对中对B成员的成员的访问就不会有二访问就不会有二义性。义性。程序设计cs.sjtu2011.9程序设计-100虚基类的概念v使公共的基使公共的基类只只产生一个拷生一个拷贝。v虚基虚基类的定的定义用关用关键词virtual。如:。如:ClassBpublic:intb;ClassB1:virtualpublicBprivate:intb1;ClassB2:virtualpublicBprivate:intb2;ClassC:pub
71、licB1,publicB2public:intf();private:intd;这样B1,B2公用了一个公用了一个B的拷的拷贝,对B成成员的引用就不会的引用就不会产生二生二义性。性。BB1B2C程序设计cs.sjtu2011.9程序设计-101虚基类的初始化v保保证虚基虚基类对象只被初始化一次。象只被初始化一次。v虚基虚基类的构造由最的构造由最终的的类的构造函数的构造函数负责。v例如,在构造例如,在构造C的的对象象时,由,由C的构造函数的构造函数负责调用用B的构造函数,而不是由的构造函数,而不是由B1、B2来来调用。用。v构造次序:先构造次序:先执行行B的构造函数,再的构造函数,再执行行B1
72、、B2的构造函数,最后的构造函数,最后执行行C的构造函数。的构造函数。v析构次序与构造次序相反。析构次序与构造次序相反。程序设计cs.sjtu2011.9程序设计-102虚基类的初始化实例#includeclassBinta;public:B(intsa)a=sa;coutconstructingBn;classB1:virtualpublicBintb;public:B1(intsa,intsb):B(sa)b=sb;coutconstructingB1n;classB2:virtualpublicBintc;public:B2(intsa,intsb):B(sa)c=sb;coutcons
73、tructingB2n;classC:publicB1,publicB2intd;public:C(intsa,intsb,intsc,intsd):B(sa),B1(sa,sb),B2(sa,sc)d=sd;coutconstructingCn;main()Cobj(2,4,6,8);return0;程序设计cs.sjtu2011.9程序设计-103虚基类的初始化实例v执行结果:constructingBconstructingB1constructingB2constructingC程序设计cs.sjtu2011.9程序设计-104第第12章章 组合与继承组合与继承v组合合v继承承v虚函数
74、与多虚函数与多态性性v纯虚函数与抽象虚函数与抽象类v多多继承承v面向面向对象象设计范例范例程序设计cs.sjtu2011.9程序设计-105龟兔赛跑问题龟兔赛跑问题v确定确定对象:象:乌龟、兔子和裁判、兔子和裁判员v类的确定:的确定:乌龟类、兔子、兔子类和裁判和裁判员类v乌龟类没有数据成没有数据成员,只有一个成,只有一个成员函函数数movev兔子兔子类没有数据成没有数据成员,只有一个成,只有一个成员函函数数move程序设计cs.sjtu2011.9程序设计-106乌龟类的定义乌龟类的定义classTortoisepublic:intmove()intprobability=rand()*10/
75、(RAND_MAX+1);if(probability5)return3; /快走快走elseif(probability7)return-6;/后滑后滑elsereturn1;/慢走慢走;程序设计cs.sjtu2011.9程序设计-107兔子类的定义兔子类的定义classHarepublic:intmove()intprobability=rand()*10/(RAND_MAX+1);if(probability2)return0; /睡睡觉elseif(probability4)return-9;/大后滑大后滑elseif(probability5)return14; /快走快走elsei
76、f(probability8)return3;/小步跳小步跳elsereturn-2;/慢后滑慢后滑;程序设计cs.sjtu2011.9程序设计-108裁判员类的设计裁判员类的设计v裁判裁判员的功能:的功能:记录乌龟和兔子在比和兔子在比赛中的位置中的位置输出它出它们的位置的位置判断比判断比赛是否是否结束并束并给出比出比赛的的结果果v裁判裁判类:两个数据成两个数据成员,分,分别为乌龟和兔子的位置和兔子的位置5个成个成员函数:函数:l更新乌龟和兔子的位子更新乌龟和兔子的位子updateupdatel输出乌龟壳兔子的当前位置输出乌龟壳兔子的当前位置printprintl判断比赛是否结束判断比赛是否结
77、束isFinishisFinish并给出胜者并给出胜者l输出报表头部的函数输出报表头部的函数l重新开始比赛。重新开始比赛。 程序设计cs.sjtu2011.9程序设计-109裁判员类的定义裁判员类的定义classJudgerinthare,tortoise;enumRACE_END=100;/赛道道长度度public:Judger()hare=tortoise=0;srand(time(NULL);voidrestart()hare=tortoise=0;/重新开始比重新开始比赛voidprint_title()const/显示示输出信息的出信息的头部部coutharettortoisen;c
78、out=n;程序设计cs.sjtu2011.9程序设计-110voidupdate(inth,intt)/更新兔子和更新兔子和乌龟的当前位置的当前位置hare+=h;tortoise+=t;voidprint()const/输出出乌龟和兔子的当前位置和兔子的当前位置coutharettortoise=RACE_END|tortoise=RACE_END)if(haretortoise)winner=0;elseif(haretortoise)winner=1;elsewinner=2;returntrue;elsereturnfalse;程序设计cs.sjtu2011.9程序设计-111龟兔赛
79、跑过程模拟龟兔赛跑过程模拟 intmain()Tortoisetort;Harehare;Judgerjudger;intwinner;judger.print_title();while(!judger.isFinish(winner)judger.update(hare.move(),tort.move();judger.print();switch(winner)/输出比出比赛结果果case0:cout兔子兔子赢了!了!;break;case1:cout乌龟赢了!了!;break;default:cout平局平局;return0;程序设计cs.sjtu2011.9程序设计-112统计统计
80、n次龟兔赛跑的结果次龟兔赛跑的结果 intmain()Tortoisetort;Harehare;Judgerjudger;intwinner,num,win_cnt_hare=0,win_cnt_tort=0,tie_cnt=0;coutnum;for(inti=0;inum;+i)/模模拟num次比次比赛judger.restart();while(!judger.isFinish(winner)/模模拟一一场比比赛judger.update(hare.move(),tort.move();switch(winner)/记录比比赛结果果case0:+win_cnt_hare;break;c
81、ase1:+win_cnt_tort;break;default:+tie_cnt;cout乌龟赢兔子兔子赢平局平局n;coutwin_cnt_torttwin_cnt_harettie_cntendl;return0;程序设计cs.sjtu2011.9程序设计-113问题问题v如果要如果要统计若干个兔子的若干若干个兔子的若干场比比赛的的结果果该怎么怎么办?程序设计cs.sjtu2011.9程序设计-114小结小结 v面向面向对象程序象程序设计的一个重要的目的一个重要的目标是代是代码重用。重用。v代代码重用的两种方法:重用的两种方法:组合和合和继承。承。组合是将某一个已定合是将某一个已定义类的的对象作象作为当前当前类的数据成的数据成员,则对此数据成此数据成员操作的代操作的代码得得到了重用。到了重用。继承是在已有承是在已有类(基(基类)的基)的基础上加以上加以扩展,展,形成一个新形成一个新类,称,称为派生派生类。在派生。在派生类定定义时,只需要,只需要实现扩展功能,而基展功能,而基类有的功能有的功能得到了重用。得到了重用。