[C++面向对象程序设计-[141页]课件

PPT
  • 阅读 45 次
  • 下载 0 次
  • 页数 141 页
  • 大小 1.249 MB
  • 2022-11-24 上传
  • 收藏
  • 违规举报
  • © 版权认领
下载文档40.00 元 加入VIP免费下载
此文档由【小橙橙】提供上传,收益归文档提供者,本网站只提供存储服务。若此文档侵犯了您的版权,欢迎进行违规举报版权认领
[C++面向对象程序设计-[141页]课件
可在后台配置第一页与第二页中间广告代码
[C++面向对象程序设计-[141页]课件
可在后台配置第二页与第三页中间广告代码
[C++面向对象程序设计-[141页]课件
可在后台配置第三页与第四页中间广告代码
[C++面向对象程序设计-[141页]课件
[C++面向对象程序设计-[141页]课件
还剩10页未读,继续阅读
【这是免费文档,您可以免费阅读】
/ 141
  • 收藏
  • 违规举报
  • © 版权认领
下载文档40.00 元 加入VIP免费下载
文本内容

【文档说明】[C++面向对象程序设计-[141页]课件.pptx,共(141)页,1.249 MB,由小橙橙上传

转载请保留链接:https://www.ichengzhen.cn/view-44700.html

以下为本文档部分文字说明:

第4章继承•继承是软件复用的一种语言机制,可以通过继承复用已有的程序资源,使软件复用变得简单、易行,缩短软件开发的周期。•本章主要介绍–继承的方式,–派生类与基类对象之间的关系–派生类构造函数如何提供对基类的构造。1.继承的概念

以存在的类为基础定义新的类,新类即捅有基类的数据成员和成员函数的一份复制品,这就是继承。Personchar*name;char*id_card_no;boolgender;Studentcharstuden

t_no;Dateenroll_date;……FacultyDatehire_date;Degreedegree;……AdministratorDatehire_date;Rankrank;……5.1继承的概念继承方式:单一继承:只有一基类多重继承:可以有多个

基类基类也叫超类,父类派生类也叫子类2、继承目的•代码重用coderesue•描述能力:类属关系广泛存在•IsAvs.HasA3、有关概念基类,超类派生类,子类5.1继承的概念4、派生类可实施的对基类的改变–增加新的数据成员和成员函数。–重载基类的成员函数。–重定义基

类已有的成员函数。–改变基类成员在派生类中的访问属性。5、派生类不能继承基类的以下内容–基类的构造函数和析构函数。–基类的友元函数。–静态数据成员和静态成员函数5.1继承的概念•基类中protected的成员–类内部:可以访问

–类的使用者:不能访问–类的派生类成员:可以访问【例4-1】类B有数据成员i,j,k,希望j可被派生类和自身访问,但不希望除此之外的其它函数访问。分析:protected权限正好具有这样的访问控制能力4.2protected和

继承classB{private:inti;protected:intj;public:intk;};classD:publicB{public:voidf(){i=1;//cannotaccessj=2;k=3;}};voidmain(){Bb;b.i=1;//cannotaccessb.j=2

;//cannotaccessb.k=3;}Kkiif()接口私有数据BDjj4.3.继承方式1、C++的继承方式–公有继承、保护继承和私有继承,也称为公有派生、保护派生和私有派生。–不同继承方式会不同程度地改变基类成员在派生类中的访问权限2、继承语法形式class

B{……};classD:[private|protected|public]B{……};基类子对象派生类新定义成员继承部分派生部分派生类对象•1、public继承–最常用的派生方式,派生类复制了基类数据成员和成员函数的一份复制

品。–派生类从基类继承到的成员,维持基类成员的可访问性。即基类的public成员在派生类中也是public成员,可被派生类的外部函数访问。–同样,一个成员若在基类是protected或private属性,它在派生类中仍然是pro

tected或private属性–派生类不可直接访问基类的private成员,可通过基类的共有成员函数访问4.3.继承方式•例题ch_1.cppclassBase{intx;public:voidsetx(intn){x=n;}intgetx(){returnx;}voidshow

x(){cout<<x<<endl;}};classDerived:publicBase{inty;public:voidsety(intn){y=n;}voidsety(){y=getx();}voidshowy(){cout<<y<<endl;}};Setx(

)Getx()Showx()xSetx()Getx()Showx()xSety()Gety()Showy()y接口私有数据basederivedvoidmain(){Derivedobj;obj.setx(10);obj.

showx();obj.showx();obj.sety();obj.sety(20);obj.showy();obj.showy();}4.3.继承方式Derived类并未定义setx等成员函数,但却调用了它们。原因是它从Base类继承了这些函数。2.Private–基类的中的pu

blic、protected成员在派生类private,private成员在派生类中不可访问。【例5】私有继承的例子#include<iostream>usingnamespacestd;classBase{intx;public:vo

idsetx(intn){x=n;}intgetx(){returnx;}voidshowx(){cout<<x<<endl;}};4.3.继承方式2、Private继承classDerived:privateBase{……}–在private派生方式下,派生复制了基类全部

成员,但复制到的成员在派生类中全部变成了private成员。–在private派生方式下,虽然基类的public和protected成员在派生类中都变成了private成员,但它们仍然有区别:派生类的成员函数不能直接访问基类的private成员,但可以直接访问基类的pub

lic和protected成员,并且通过它们访问基类本身的private成员。4.3.继承方式classderived:privatebase{inty;public:voidsety(intn){y=n;}voidsety(){y=getx

();}voidshowy(){cout<<y<<endl;}};voidmain(){derivedobj;obj.setx(10);//cannotaccessobj.sety(20);obj.showx();//cannot

accessobj.showy();}Setx()Getx()Showx()xSetx()Getx()Showx()xSety()Gety()Showy()y接口私有数据basederived3、protected继承classDerived

:protectedBase{……}–在protected派生方式下,派生复制了基类全部成员,但复制到public成员在派生类中变成了protected成员,其余成员的访问权限保持不变。–在派生方式下,派生类的继承到的成员函数都不能被外部函数访问,但在派生类中可以直

接访问基类的public和protected成员,并且通过它们访问基类本身的private成员。4.3.继承方式【例】保护继承的例子。#include<iostream>usingnamespacestd;cl

assBase{intx;protected:intgetx(){returnx;}public:voidsetx(intn){x=n;}voidshowx(){cout<<x<<endl;}};4.3.继承方式classDerived:protectedBas

e{inty;public:voidsety(intn){y=n;}voidsety(){y=getx();}//访问基类的保护成员voidshowy(){cout<<y<<endl;}};voidmain(){Derivedobj;obj.setx(10);//错误obj.showx();//错

误,obj.sety(20);obj.showy();}Protected继承方式已将setx,showx改变成了protected成员,不能在派生类外直接访问4.3.继承方式•基类成员在派生类中的访问权限不能继承的基类内容1.

构造函数、析构函数2.友员关系3.针对基类定义的一些特殊运算符,如new等。派生类基类public继承protected继承private继承publicprotectedprivatepublicprotectedprivatepublicprotect

edprivatepublic√√√protected√√√private√√√4.阻止继承11C++–如果不想让一个类作为其它类的基类,可以用final关键字阻止它被继承。形式如下:classBase{……}//可以被继承classNoDerifin

al{……}//不能被继承classDfinal:Base{……}//正确,D不能被继承classD1:NoDeri{……}//错误,NoDeri不能被继承classD2:D{……}//错误,D不能被继承4.3.继承方式1、派生类和基类的关系–派生类拷贝了基类数据成员和成员函数的一

份副本,不用编程就具备了基类的程序功能。–在派生类对象中,具有一个基类子对象。classBase{basemembers;};classDerived:Base{newmembers;};basemembersnewmemb

ersthis指针Derivedmembers4.4派生类对基类的扩展2、派生类和基类成员的修改和扩展–派生类可以在继承基类成员的基础上①派生类可以增加新的数据成员和成员函数;②重载从基类继承到的成员函数;③覆盖(重定义)从基类继承到的成员函数;④改变基类成员在

派生类中的访问属性。3、派生类不能继承基类的以下成员①析构函数。②基类的友元函数。③静态数据成员和静态成员函数。4.4派生类对基类的扩展4.4.1成员函数的重定义和名字隐藏1、成员函数的重定义和名字隐藏–派生类对基类成员函数的重定义或重载会影响基类成员函数在派生类中的可见性,基类的同名成

员函数会被派生类重载的同名函数所隐藏。【例4-3】设计计算矩形与立方体面积和体积的类。(1)问题分析–矩形具有长和宽,面积=长×宽,没有体积,可以设置为0–具有高的矩形就是立方体,面积=2×底面积+2×侧面积1+2×侧面积2,体积=底面积×高。其中的底

面积就是矩形的面积。–立方体是对矩形的扩展,矩形完成了长和宽的处理,在此基础上完成高的处理就能够实现其功能。这一关系可以通过继承实现。4.4.1成员函数的重定义和名字隐藏(2)数据抽象•将矩形抽象成类Rectangle,用数据成员width、length表宽与长,为了方便其派

生类访问数据成员,将它们设置为protected访问权限;并设置成员函数setWidth、setLength、getWidth、getLength来设置和获取矩形的宽和长,area和volume成员函数计算矩形的面积和体积,成员函数outData输出矩形的长和宽。•将立方体抽象成类Cube,并从R

ectangle类派生,由于Rectangle类已经完成了矩形长和宽的处理功能,所以只须增加数据成员high表示高,并设置setHigh和getHigh成员函数完成对高的读、写功能。•虽然Rectangle类已经设置了area、volume

和outData成员函数计算面积、体积,或输出数据成员的值,但立方体的面积、体积和数据成员是不同的,需要重新定义它们•矩形和立方体的抽象结果•注意Cube对基类继承到的成员函数的重定义会隐藏继承于基类的同名函数4.4.1成员函数的重定义和名字隐藏//Eg4-3.cpp#include

<iostream>usingnamespacestd;classRectangle{public:voidsetLength(doubleh){length=h;}voidsetWidth(doublew){width=w;}doublegetLength(){

returnlength;}doublegetWidth(){returnwidth;}doublearea(){returnlength*width;}doublevolume(){return0;}voidoutData(){cout<<"lenggh="<<length<<”\t”

<<"width="<<width<<endl;}protected:doublewidth;doublelength;};4.4.1成员函数的重定义和名字隐藏classCube:publicRect

angle{public:voidsetHigh(doubleh){high=h;}doublegetHigh(){returnhigh;}doublearea(){returnwidth*length*2+width*high*2+length*high*2;

}//L1doublevolume(){returnRectangle::area()*high;}voidoutData(){Rectangle::outData();//L2派生类访问基类同名成员的方法cout<<"high="<<high<<endl;}priv

ate:doublehigh;};4.4.1成员函数的重定义和名字隐藏voidmain(){Cubecub1;cub1.setLength(4);cub1.setWidth(5);cub1.setHigh(3);cub1.R

ectangle::outData();//访问基类继承到的同名成员cub1.outData();//访问派生类的同名成员cout<<"立方体面积="<<cub1.area()<<endl;cout<<"立方体底面积="<<cub1.Rectangle::area()<<endl;cou

t<<"立方体体积="<<cub1.volume()<<endl;}4.4.1成员函数的重定义和名字隐藏•派生类对基类成员的访问有以下形式1.通过派生类对象直接访问基类成员,如cub1.setWidth(5);2.在派生类成员函数中直接访问基类成员,如前面Cube类的L1语句。3.通过

基类名字限定访问被重载的基类成员名•在派生类成员函数中访问基类同名成员函数–如Cube的volume()和area()成员函数•在派生对象中访问基类同名成员函数–cub1.Rectangle::outData();4.4.2基类成员访问4.4.3using与隐藏函数重现1

1C++1.派生对基类同名成员的隐藏–如果基类某个成员函数具有多个重载的函数版本,派生类又定义了同名成员,就会隐藏基类同名的全部重载函数。2.访问隐藏成员①一是使用基类名称限定要访问的成员函数②重载基类的所有同名函数,而这些重载函数的代码与基类完全相同③用using声明使基类重载函数

在派生类中可见。用法如下:using基类名称::被隐藏成员函数名;【例4-4】基类B的f1成员函数具有3个重载函数,派生D新增加了f1函数的功能,此f1会隐藏基类B中f1函数在派生类的可见性,用using将基类的f1引入到派生类作用域内。#include

<iostream>usingnamespacestd;classB{public:voidf1(inta){cout<<a<<endl;}voidf1(inta,intb){cout<<a+b<<endl;}voidf1(){cout<

<"B::f1"<<endl;}};classD:publicB{public:usingB::f1;//L1,使基类的3个f1函数在此区域可见voidf1(char*d){cout<<d<<endl;}};voidmain(){Dd;d.f1();//L2,正确

,调用基类成员d.f1(3);//L3,正确,调用基类成员d.f1(3,5);//L4,正确,调用基类成员d.f1("Hellowc++!");}4.4.4派生类修改基类成员的访问权限1.修改原由–不同继承方式可能会改变基类成员在派生类中的访问权

限。–比如,在private继承方式下,基类的public和protected成员在派生类中的访问权限都会被更改为private访问权限。–在某些时候,从类的整体设计上考虑,需要调整个别基类成员在派生类中的访问权限,使用using声明可以实现这一

目的。2.修改方法在派生类的public、protected或pirvate权限区域内,使用using再次声明基类的非private成员,就可以重新设置它们在派生类中的权限为using语句所在区域的权限。即using语句在public区域内即为pu

blic权限,在protected内即为protected权限,在private内则为private权限。4.4.4派生类修改基类成员的访问权限【例4-5】类D私有继承了类Base,修改基类Base成员在派生类中的访问权限,设置基类成员y在派生类的权限为private,其余成员在派生类

中的权限保持与其在基类中的相同权限。#include<iostream>usingnamespacestd;classBase{public:intx=0;voidsetxyz(inta,doubleb,floatc){x=a;y=

b;z=c;}protected:doubley=0;floatgetZ(){returnz;}private:floatz=0;};classD:privateBase{protected:usingBase::getZ;//指定getz为prote

cted权限//usingBase::z;//错误,不允许修改基类private成员public:usingBase::x;//指定x为public权限usingBase::setxyz;//指定setxyz为public权限voiddisplay(){cout<<"x="<

<x<<"\ty="<<y<<"\tz="<<getZ()<<endl;}private:usingBase::y;//指定y为private权限};voidmain(){Dd;d.setxyz(8,9

,10);d.display();}4.4.4派生类修改基类成员的访问权限Private继承使基类成员在派生类中都成私有成员4.4.5友元与继承•友元不能被继承–每个类只能够负责控制自已的成员的访问权限。–因此,如果一个类继承了其它类,则它声明的友元也只能访问它自己的全体成员,包括它从基类继承到

的public和protected成员。而它的基类和派生类并不认可这种友元关系,按照规则只能访问公有成员。•【例4-6】类Deri是基类Base的友元,函数f1和f2是类Deri的友元,分析下面程序中L4、L5、L7正确的原因,以及L8和L6错误的原因。4.4.

5友元与继承#include<iostream>usingnamespacestd;classBase{public:intx=0;protected:doubley=0;private:floatz=0;friendclassDeri;//L1Deri为B

ase的友元};//Base的全体成员可访问x,y,zclassDeri:publicBase{protected:intdx=1;public:friendvoidf1(Derid);//L2friendvoidf2(Baseb);//L3voidf3(Baseb){cout<<b.x

<<b.y<<b.z<<endl;}//L4,正确};voidf1(Derid){cout<<d.x<<d.y<<d.dx<<endl;//L5,正确//cout<<d.z<<endl;//L6,错误}voidf2(Baseb){cout<<b.x<<endl;//L7,正确//cout<<b.

y<<endl;//L8,错误}voidmain(){Baseb;Derid;f1(d);f2(b);4.4.6静态成员与继承1.基类静态成员为继承层次结构所有类共享–在继承体系中,如果基类定义了静成成员,则在整个继承体系中只有

该成员的唯一定义,不论从该基类派生出了多少个或多少层次的派生类,静态成员都只有一个实例,为整个继承体系中的全体对象所共用。2.基类静态成员在继承结构中的应用–设计全类公用数据,或统计继承体系中的对象个数:将共享数据或计数器设置为基

类的静态成员,就能够实现这样的目的。4.4.6静态成员与继承【例4-7】假设父亲生了儿子、女儿,儿子又生有孙子,构成了家族继承体系,统计家族成员的人数。问题分析:用Father、Son、Daugther、Frandson分别表示父亲类、儿子类、女儿类和孙子类,它

们通过继承形成了层次结构的继承体系。数据抽象在Father类中设计静态成员personNum统计家族的人数,每构造一个对象人数就增加1,每析构一个对象就减少1;由于每个人都有姓名,因此在基类Father中设置name数据成员代表人名。为了便于派生类访问personNum和nam

e成员,把它们设置为protected访问权限。4.4.6静态成员与继承#include<iostream>#include<string>usingnamespacestd;classFather{protected:stringname;staticintpersonNum

;public:Father(stringName=""):name(Name){personNum++;}~Father(){personNum--;}staticintgetPersonNumber(){returnpersonNum;}};intF

ather::personNum=0;classSon:publicFather{public:Son(stringname):Father(name){}};classDaugther:Father{public:Daugthe

r(stringname):Father(name){}};classGrandson:publicSon{public:Grandson(stringname):Son(name){}};voidmain(){Fatherson("tom");Sonsson("

jack");Daugtherdson("mike");{Grandsongson("s.jack");cout<<son.getPersonNumber()<<endl;//L1,输出4}cout<<son.getPersonNumber()<<endl;//

L2,输出3}到L1语句时,总共定义了4个对象,都记在了由基类定义的共享静态成员personNum中,因此L1语句输入44.4.7继承与类作用域1.基类类域–每个类都建立了属于自己的作用域,本类的全体成员都位于此作用

域内,而且相互之间可以直接访问,不受定义先后次序的影响。–例如,一个成员函数可以调用在它后面定义的另一个成员函数。2.派生类类域–派生类的作用域嵌套在基类作用域的内层。–在解析类成员名称时,如果在本类的作用域内没有找到,编译器就会接着在外层的基类作用域内继续寻找该成员名称的定义。

4.4.7继承与类作用域3.编译器解析之后的派生类类域形式–例如类A、B、C继承形式如下classA{intg();……};classB:publicA{inth(int);……};classC:publicB{intc;in

th();intf(int);……};4.4.7继承与类作用域经编译器处理之后,形成类似于下面的块作用域:A{intg(){……}……B{inth(int){……};……C{intc;inth(){……};intf(inti){……;ret

urnB::h(i);}//L1……}}}Cxa;xa.g();xa.h(3);//L2,错误xa.B::h(3);//L3,正确思考题:1.分析xa.g()的调用过程?2.分析L2错误的原因?4.5构造函数和析构函数1.为什么要设计构造函数?–在

任何时候,只要定义类的对象,就需要调用适当的构造函数。因此,设计类时必须要考虑类的构造函数设计(包括派生类)。–有时,一个类没有构造函数也在使用,这种情况只能定义无参对象,而且它调用了编译器为它生成的默认构造函数(这种情况一定符合编译器为类自动生成默认构造函数的情况)。2.如何设

计构造函数–在用类定义对象时,通常会用到默认构造函数(定义无参对象或对象数组)、拷贝构造函数(类对象作函数参数),赋值运算符函数(对象赋值),移动构造函数,移动拷贝构造函数和移动赋值运符函数,当类没有定义任何构造函数时,编译器在需要来会

自动为类生成这些成员函数。–在通常情况下,由编译器生成的上述成员函数已能够胜任对象的定义或复制了,即可以不定义这些成员函数。–但是,当类存在指针数据成员时,就很有可能需要显示定义这些成员函数,否则很有可能产生指针悬挂问题。4.5.1派生类构造函数的建立规则1.派生类只能在构造函数初始

化列表中为基类或对象成员进行初始化–当派生类有(多个)基类和(多个)对象成员,派生类的构造函数除了要负责本类成员的初始化外,还要调用基类和对象成员的构造函数,并向它们传递参数,以完成基类子对象和对象成员的建立和初始化。–派生类只能采用构造

函数初始化列表的方式向基类或对象成员的构造函数传递参数,形式如下:派生类构造函数名(参数表):基类构造函数名(参数表),对象成员名1(参数表),……{……}【例】派生类Derived以构造函数初始化列表的方式向基类构造函数

提供参数。#include<iostream>usingnamespacestd;classBase{private:intx;public:Base(inta){x=a;cout<<"Baseconstructorx="<<x

<<endl;}~Base(){cout<<"Basedestructor..."<<endl;}};4.5.1派生类构造函数的建立规则classDerived:publicBase{private:inty;public:Derived(inta,intb):Base(a){/

/派生类构造函数的初始化列表y=b;cout<<"Derivedconstructory="<<y<<endl;}~Derived(){cout<<"Deriveddestructor..."<<endl;}};voidmain(){Derivedd(1,2);}4

.5.1派生类构造函数的建立规则2、派生类必须定义构造函数的情况–当基类或成员对象所属类只含有带参数的构造函数时,即使派生类本身没有数据成员要初始化,它也必须定义构造函数。–派生类构造函数以初始化列表的方式向基类和成

员对象的构造函数传递参数,以实现基类子对象和成员对象的初始化。4.5.1派生类构造函数的建立规则【例】派生类构造函数的定义。#include<iostream>usingnamespacestd;classPoint{protected:intx,y;public:Point(inta,intb=

0){x=a;y=b;cout<<"constructingpoint("<<x<<","<<y<<")"<<endl;}};4.5.1派生类构造函数的建立规则classLine:publicPoint{protected:intlen;public:Line(

inta,intb,intl):Point(a,b){//构造函数初始化列表len=l;cout<<"ConstructingLine,len..."<<len<<endl;}};voidmain(){LineL1(1,2,3);}Line类必须在构造函数列表中向基类Point

类构造函数提供初值!4.5.1派生类构造函数的建立规则3、派生类可以不定义构造函数的情况•当具有下述情况之一时,派生类可以不定义构造函数。①基类没有定义任何构造函数。②基类具有缺省参数的构造函数。③基类具有无参构造函

数。【例4-9】类A具有默认构造函数,其派生类B没有成员要初始化,不必定义构造函数。4.5.1派生类构造函数的建立规则//Eg4-9.cpp#include<iostream>usingnamespacestd;classA{public:A(){cout<<"

ConstructingA"<<endl;}~A(){cout<<"DestructingA"<<endl;}};classB:publicA{public:~B(){cout<<"DestructingB"<<endl;}};voidmain(){Bb;}4.5.1派生类构造函数的建立规则程序

运行结果:ConstructingADestructingBDestructingA此结果表明,在定义对b时,调用了编译器为类B自动合成的默认构造函数,此函数类似于下面的形式:B::B():A(){}3、派生类的构造函数只负责直接基类

的初始化•C++语言标准有一条规则:如果派生类的基类同时也是另外一个类的派生类,则每个派生类只负责它的直接基类的构造函数调用。•这条规则表明当派生类的直接基类只有带参数的构造函数,但没有默认构造函数时(包括缺省参数和无参构造函数),它必须在

构造函数的初始化列表中调用其直接基类的构造函数,并向基类的构造函数传递参数,以实现派生类对象中的基类子对象的初始化。•这条规则有一个例外情况,当派生类存在虚基类时,所有虚基类都由最后的派生类负责初始化。4

.5.1派生类构造函数的建立规则【例4-10】类C具有直接基类B和间接基类A,每个派生类只负责其直接基类的构造。//Eg4-10.cpp#include<iostream>usingnamespacestd;classA{intx;public:A(intaa){x=aa;cout<<"

ConstructingA"<<endl;}~A(){cout<<"DestructingA"<<endl;}};classB:publicA{public:B(intx):A(x){cout<<"ConstructingB"<<endl;}};classC:publicB{public:

C(inty):B(y){cout<<"ConstructingC"<<endl;}};voidmain(){Cc(1);}ConstructingAConstructingBConstructingCDestructingA此运

行结果表明,在定义C的对象时,基类A,B的构造函数都被调用了•5.派生类继承基类的构造函数11C++①关于构造函数继承–C++11新增加标准(以前不允许继承构造函数)–解决的问题:•当基类具有多个重载构造函数,或构造函

数具有较多参数,而派生类又没有数据成员需要初始化,但它却必须提供构造函数,其唯一目的是为基类构造函数提供初始化值。在这种情况下,可以派生类可以继承直接基类的构造函数。4.5.1派生类构造函数的建立规则②构造函数继承方法用

using在派生类中声明基类构造函数名即可。形式如下:classBase:{……}classDerived:[public]Base{//也可以是private或protected继承……usingBase::Base;//继承基类

构造函数}4.5.1派生类构造函数的建立规则例4-11】类A具有数据成员x,y,并且定义了初始化它们的构造函数;类B从A派生,没有任何成员要初始化;类C从类B派生,具有新定义数据成员c。设计A、B、C的构造函数。问题分析:–按照规则,类B虽然没有数据成员要初始化,但是它必须为基类A的构

造函数提供初值(除非A具有默认构造函数),现在,可以通过继承A的构造函数使问题更简单。–类C要定义构造函数以便初始化其成员c,同时还必须为直接基类B提供构造初值。4.5.1派生类构造函数的建立规则///Eg4-11.cpp#incl

ude<iostream>usingnamespacestd;classA{intx,y;public:A(intaa):x(aa){cout<<"ConstructingA:x=\t"<<x<<endl;}A(inta,intb):x(a),y(b){

cout<<"ConstructingA:x=\t"<<x<<endl;}};classB:publicA{public:usingA::A;//L1/*B(intx):A(x){//L2cout<<"Constructin

gB\t"<<endl;}*/};L1声明类B继承了A的构造函数,编译会为类B自动生成相应的程序代码,类似于:B::B(inta):A(a){}B::B(inta,intb):A(a,b){}不论类A有多少构造函数,系统都会自动生成。如果没有L1,就需像L2一样为每个基类构造函数提

供程序代码。classC:publicB{usingB::B;//L3intc;public:C(intx,inty,intz):B(x,y),c(z){//L4cout<<"ConstructingC:\t"<<c<<end

l;}};voidmain(){Bb1(1),b2(8,9);//L5Cc1(1),c2(3,4);//L6}4.5.1派生类构造函数的建立规则C继承了B的构造函数,省去了为B具有1个参数和2个参数的两个构造函数编写程序代码③构造继承的几点说明a)“Base::Base”即为基类

名和基类构造函数的名称,using语句说明了派生类要继承基类的构造函数。如果基类有多个构造函数,则using语句会在派生类中为每个基类构造函数生成一个与之对应的构造函数,并具有与基类构造函数相同的访问权限。b)using不受访问权限制约,放在public、protected或

private区域中没有区别。c)用using在派生类中声明基类的构造函数和其它成员有所不同,声明其它成员只是使该成员在指定的派生类权限区域可见,并不生成代码。而用using继承基类构造函数,则会使编译器在

派生类中生成基类构造函数的一份副本。d)基类的默认构造函数、拷贝构造函数和移动构造函数不能够被继承e)若派生类在继承基类构造函数的同时,还需要定义其它构造函数,必须在构造函数初始化列表中为基类构造函数提供初始

化值(除非基类有默认构造函数)。4.5.1派生类构造函数的建立规则4.5.1派生类构造函数的建立规则f)如果基类构造函数具参数默认值,这些默认值不会被继承,继承将为派生类生成多个构造函数,每个构造函数的参数依次少一个。例如,classA{intx,y;pu

blic:A(inta,intb=2):x(a),y(b){cout<<"a="<<a<<"\tb="<<b<<endl;}};classB:publicA{public:usingA::A;};–继承将为类B生成构造函数:B(i

nta):A(a,2)和B(inta,intb):A(a,b)4.5构造函数和析构函数•补充:类对象成员的构造–先构造对象成员–再构造自身(调用构造函数)•例题ch.cppclassA{public:A(){cout<<"ConstructingA"<<endl;}~A(){cout<<

"DestructingA"<<endl;}};classB{public:B(){cout<<"ConstructingB"<<endl;}~B(){cout<<"DestructingB"<<endl;}};classC{public:C(){cout<<"

ConstructingC"<<endl;}~C(){cout<<"DestructingC"<<endl;}Bb;Aa;};voidmain(){Cc;}ConstructingBConstructingAConstructingCDestructingCDestru

ctingADestructingB如果:classB:publicA{}classC:publicB{}结果又当如何?5.4.1派生类构造函数的定义•派生类可能有多个基类,也可能包括多个成员对象,在创建派生类对象时,派生类的构造函数

除了要负责本类成员的初始化外,还要调用基类和成员对象的构造函数,并向它们传递参数,以完成基类子对象和成员对象的建立和初始化。•派生类只能采用构造函数初始化列表的方式向基类或成员对象的构造函数传递参数,形式如下:派生类构造函数名(参数表):基类构造函数名(参数表),成员对象名1(参

数表),…{//……}1.派生类构造函数构造原则和次序基类构造函数→对象成员构造函数→派生类构造函数①当有多个基类时,将按照它们在继承方式中的声明次序调用,与它们在构造函数初始化列表中的次序无关。–当基

类B本身又是另一个类A的派生类时,则先调用基类A的构造函数,再调用基类B的构造函数。②当有多个对象成员时,将按它们在派生类中的声明次序调用,与它们在构造函数初始化列表中的次序无关。③当构造函数初始化列表中的基类和对象成员的构造函数调用完成之后,才执行派生类构造函

数体中的程序代码。4.5.2派生类构造函数和析构函数的调用次序2、构造函数的调用原则和次序•当派生类具有多个基类和多个对象成员,它们的构造函数将在创建派生类对象时被调用,调用次序如下:基类构造函数→对象成员构造函数→派生类构造函数(1)当有多个基类时,将按照它们在继承方式中的声明次序调用,与

它们在构造函数初始化列表中的次序无关。当基类A本身又是另一个类B的派生类时,则先调用基类B的构造函数,再调用基类A的构造函数。(2)当有多个对象成员时,将按它们在派生类中的声明次序调用,与它们在构造函数初始化列表中的次序无

关。(3)当构造函数初始化列表中的基类和对象成员的构造函数调用完成之后,才执行派生类构造函数体中的程序代码。4.5.2派生类构造函数和析构函数的调用次序【例4-12】类D从类B派生,并具有用类A和C建立的对象成员。分析创建D的对象时,基类、对象成员和派生类构造函数和析构函数

的调用次序。//Eg4-12.cpp#include<iostream>usingnamespacestd;classA{intx;public:A(inti=0):x(i){cout<<"ConstructA----"<<x<<endl;}~A(){c

out<<"DesA----"<<x<<endl;}};4.5.2派生类构造函数和析构函数的调用次序classB{inty;public:B(inti):y(i){cout<<"ConstructB----"<<y<<end

l;}~B(){cout<<"DesB----"<<y<<endl;}};classC{intz;public:C(inti):z(i){cout<<"ConstructC----"<<z<<endl;}~C(){cout<<"DesC----"<<z<<endl;}};4.5.2派生类构造函数和

析构函数的调用次序classD:publicB{public:Cc1,c2;Aa0,a4;D():a4(4),c2(2),c1(1),B(1){cout<<"ConstructD----5“<<endl;}~D(){cout<<"DesD----

5"<<endl;}};voidmain(){Dd;}4.5.2派生类构造函数和析构函数的调用次序运行结果如下,分析每个输出的来源ConstructB----1ConstructC----1ConstructC----2ConstructA----0ConstructA----4Con

structD----5DesD----5DesA----4DesA----0DesC----2DesC----1DesB----14.5.3派生类的赋值、拷贝和移动操作1.派生类赋值、拷贝和移动操作对基类的职责(1)派生类的赋值函数和拷贝构造函数,以及移动赋值和移动构造函数不但要执

行派生类成员的拷贝和移动,而且还要负责基类部分数据成员的拷贝和移动。(2)如果一个类没有定义赋值运算、拷贝构造函数、移动赋值和移动构造函数,编译器将会为它们自动生成对应的函数版本。但以下两种情况除外:①当一个类有虚析构函数时,即使没有定义这些函数,编译器也

不会合成它们。②如果一个类定义了赋值运算符或拷贝构造函数,编译器也不会为它合成移动赋值和移动构造函数。(3)派生类在定义赋值函数、拷贝构造函数和它们的移动函数版本时,要负责对基类成员进行相应的处理,即应当调用基类与之对应的赋值函数、拷贝构造函数和移动函数来完成基类成员的相应处理。【例4-1

3】类A具有数据成员x,并定义了赋值函数,拷贝构造函数和它们的移动函数版本,以实现对象间的赋值、拷贝或移动操作,类B从类A派生,并有数据成员y。设计类B的赋值、拷贝构造函数和移动函数,实现派生类B的对象间的赋值、拷贝和移动操作。设计思路:根据前面的规则,当一个类设计了赋值运

算符函数、拷贝构造函数和移动函数时,就需要在这些函数中提供对基类对应函数的初始化支持。因此,在类B的相应函数设计中,要提供对基类A对应函数的初始化列表。4.5.3派生类的赋值、拷贝和移动操作//Eg4-13.cpp#include<iostream>usingnamespac

estd;classA{intx;public:A(inta=0,intb=2):x(a){}A&operator=(A&o){x=o.x;cout<<"InA=(A&)"<<endl;return*this;}A&operator=(A&&o)=default;//使用默认的合成移动

赋值函数A(A&o):x(o.x){cout<<"InA(&)"<<endl;}A(A&&o):x(std::move(o.x)){cout<<"InA(&&)"<<endl;}};4.5.3派生类的赋值、

拷贝和移动操作classB:publicA{inty;public:B(inta=0,intb=0):A(a),y(b){}B&operator=(B&o){A::operator=(o);cout<<"InB=(B&)"<<endl;return*this;}B&operat

or=(B&&o){A::operator=(std::move(0));cout<<"InB=(B&&)"<<endl;return*this;}B(B&o):A(o){cout<<"InB(&)"<<endl;}B(B&&o):A(std::move(o)){c

out<<"InB(&&)"<<endl;}};4.5.3派生类的赋值、拷贝和移动操作voidmain(){Bb,b1(1,2);b=b1;//L1Bb2(b);//L2Bb3=std::move(B(8,9));//L3b1=std::move(

b3);//L4}程序运行结果如下:InA=(A&)//L1的输出InB=(B&)//L1的输出InA(&)//L2的输出InB(&)//L2的输出InA(&&)//L3的输出InB(&&)//L3的输出InB=(B&&)//L4的输出4.5.3派生类的赋值、拷贝和移动操作4.6基类与派生

类对象的关系1.派生对象与基类对象的赋值相容关系–派生类通过继承获得了基类成员的一份拷贝,这份拷贝构成了派生类对象内部的一个基类子对象。–因此,公有派生方式下,凡是需要基类对象的地方都可以使用派生类对象

。基类对象能够解决的问题,用派生类对象也能够解决。称为赋值相容。包括下面三种情况:①把派生类对象赋值给基类对象;②把派生类对象的地址赋值给基类指针;③或者用派生类对象初始化基类对象的引用。2.派生类与基类赋值相容的处理方式–因为任何一个派生类对象的内部都包含有一个基

类子对象,在进行派生类对象向基类对象的赋值时,C++采用截取的方法从派生类对象中复制其基类子对象并将之赋值给基类对象。4.6基类与派生类对象的关系4.6.1派生类对象对基类对象的赋值和初始化•派生类中基类之间的对象复制关系–以下两种操作并不存在从派生类向基类的类型转换,本质上是执行基类对象的复

制构造函数或赋值运算符函数,通过它们把派生类对象中从基类继承到的数据成员复制给基类对象①在把派生类对象赋值给基类对象②用派生类对象初始化基类对象注意:不存在基类对象向派生类对象的复制关系【例4-14】类B从类A派生,设计类B的复制构造函数和赋值运算符函数

,并验证把派生对象赋值给基类对象或通过它初始化基类对象时,相关函数的调用情况。//Eg4-14.cpp#include<iostream>usingnamespacestd;classA{inta;public:voidsetA(intx){a=x;}intgetA(){returna;}A():

a(0){cout<<"A::A()"<<endl;}A(A&o):a(o.a){cout<<"A::A(&o)"<<endl;}A&operator=(Ao){a=o.a;cout<<"A::operaotor="<<endl

;return*this;}};classB:publicA{intb;public:voidsetB(intx){b=x;}intgetB(){returnb;}B():b(0){cout<<"B::B()"<<endl;}B(B&o):b(o.b){

cout<<"B::B(&o)"<<endl;}B&operator=(Bo){b=o.b;cout<<"B::operaotor="<<endl;return*this;}};voidmain(){Aa1,*pA

;Bb1,*pB;b1.setA(2);a1=b1;b1.setA(10);Aa2=b1;a2.setA(1);cout<<a1.getA()<<endl;//L1,输出2cout<<b1.getA()<<endl;//L2,输出10cout<

<a2.getA()<<endl;//L3,输出1//a2.setB(5);//L4,错误//b1=a1;//L5,错误}程序运行结果如下:A::A()A::A()B::B()A::A(&o)A::opera

otor=A::A(&o)2101请据上面的复制和赋值原则,分析此程序结果的函数调用情况4.6.1派生类对象对基类对象的赋值和初始化4.6.2派生类对象与基类对象的类型转换1.派生类和基类之间的类型转换关系(1)可以把派生类对象转换成基类对象,不能

把基类对象转换成派生类对象(无法转换出派生类新增加的成员)(2)派生类对象到基类对象的隐式类型转换–用派生类对象赋值或初始化基类对象时,实际是通过赋值运算符函数或拷贝构造函数完成的,并没有执行类型转换;当把基类对象的指针或引用绑定到派生对象时,编译器会自动执行从派生类对象到

基类对象的隐式类型转换。例如,对于例4-14的基类A和派生类B,下面的语句段会发生类型转换。Bb,b1,b2;A*pa=&b1;//正确,执行派生类向基类的转换A&rA=b2//正确,执行派生类向基类的转换Aa=b;//正确,没有类型转换,通过基类拷贝构造函数初如化

a•注意:不论以哪种方式把派生类对象赋值给基类对象,都只能够访问到派生类对象中的基类子对象的成员,不能访问派生类的自定义成员4.6.2派生类对象与基类对象的类型转换(3)基类对象到派生类对象的类型转换–实际上,不能把基类对象直接转换成派生类对象。但是,当基

类对象的指针或引用实际绑定的是一个派生类对象时,则可以将它再次转换成派生类对象。–若要进行上面所说的类型转换,只能进行强制类型转换,编译器是不会进行这种转换的隐式转换的。–例如,对例4-14中的基类A和派生类B,Aa,*pa;Bb,*pb;b=a;//错误,不允许从基类向派生类的

转换B*pb=pa;//错误,不能把基类对象的地址赋值给指向派生类对象的指针B&rB=rA;//错误,不能把基类对象作为派生类对象的引用B*pb=static_cast<B*>(pa);//正确,强制转换B&rB=s

tatic_cast<B&>(rA);//正确,强制转换4.6.2派生类对象与基类对象的类型转换3.对象、指针和引用的区别–把派生类对象赋值给基类对象或用派生类对象初始化基类对象,完成赋值或初始化操作后,基类对象与派生对象就没有关系

了–把基类对象的指针或引用绑定到派生类对象时,指针或引用从来就没有生成新对象,它们操作的是派生类对象内部的基类子对象。–如对例4-14的基类A和派生类B,有下面的代码段voidmain(){Bb,b1;Aa=b,*pa=&b1,&rA=b1;//L1b

.setA(10);//L2a.setA(9);//L3pa->setA(20);//L4rA.setA(1);//L5cout<<b.getA();//L6,输出10,并未受L3的影响cout<<b1.getA();//L7,输出1,L5设置引起的值变化}4.6.2派

生类对象与基类对象的类型转换4.派生类对象作为函数参数传递给基类对象如果函数形式参数是基类对象,也可以用派生类对象作为实参。–比如,对例4-14的类A和类B,有下面的程序段voidf1(Aa,intx){a.setA

(x);}voidf2(A*pA,intx){pA->setA(x);}voidf3(A&rA,intx){rA.setA(x);}voidmain(){Bb;b.setA(1);f1(b,10);//b.a未被f1修改,仍然

为1f2(&b,10);//b.a被f2修改为10f3(b,15);//b.a被f3修改为15}(1)形参是基类对象–不能修改实参对象的值。–参数传递方式:调用实参对象(若实参是派生类对象,则调用该对象的基类拷贝构造函数)的拷贝构造函数把

实参的数据成员复制给形参对象,参数传递完成后,实参和形参就没有关系了,因此不能改变实参对象的值。(2)形参是基类对象的引用或指针–能够修改实参对象的值。–参数传递方式:将形参绑定到实参对象(若形参是派生类对象,编译器将进行隐式类型转换,

形参引用或指针被绑定到派生类实参对象内部的基类子对象上),形参操作的实际上是实参对象本身。因此,这两种参数传递方式都能够修改实参对象的值。4.6.2派生类对象与基类对象的类型转换4.7多重继承4.7.1多继承的概念和应用1.概念–C++允许一个类从一个或多个基类派生。如果一个类

只有一个基类,就称为单一继承。–如果一个类具有两个或两个以上的基类,就称为多重继承。2.多继承的形式如下:class派生类名:[继承方式]基类名1,[继承方式]基类名2,…{……};•其中,继承方式可以是public、protect

ed、private4.7.1多继承的概念和应用【例4-15】假设有3个类Base1、Base2、Base3,其中Base1有公有成员函数setx,保护成员函数getx,私有数据成员a;Base2有公有成员函数sety和gety,私有数据成员y;Base3有公有

成员函数setz和getz,私有成员z;类Derived从这3个类派生,且有自己的公有成员函数setd、display和私有数据成员d【例4-15】上图的简单程序。//Eg4-15.cpp#include<ios

tream>usingnamespacestd;classBase1{private:intx;protected:intgetx(){returnx;}public:voidsetx(inta=1){x=a;}};4.7.1多继承的概

念和应用classBase2{private:inty;public:voidsety(inta){y=a;}intgety(){returny;}};classBase3{private:intz;public:voidsetz(inta){z=a;}intgetz(){r

eturnz;}};4.7.1多继承的概念和应用classDerived:publicBase1,publicBase2,publicBase3{private:intd;public:voidsetd(inta){

d=a;}voiddisplay();};voidDerived::display(){cout<<"Base1....x="<<getx()<<endl;cout<<"Base2....y="<<gety()<<endl;cout<<"Base3....z="<<getz()<<

endl;cout<<"Derived..d="<<d<<endl;}voidmain(){Derivedobj;obj.setx(1);obj.sety(2);obj.setz(3);obj.setd(4);ob

j.display();}4.7.1多继承的概念和应用运行结果如下:Base1....x=1Base2....y=2Base3....z=3Derived..d=4Derived类通过多继承具备了3个基类的成员,即使没有添加任何程序代码,也具有3

个基类合起来才有的强大功能!4.7.2多重继承方式下成员名的二义性•在多继承方式下,派生类继承了多个基类的成员,当两个不同基类拥有同名成员时,容易产生名字冲突问题。【例4-16】类A和类B是MI的基类,它们都有一个成员函数f

,在类MI中就有通过继承而来的两个同名成员函数f,在调用时易产生冲突。//Eg4-16.cpp#include<iostream>usingnamespacestd;classA{public:voidf(){cout<<"FromA"<<endl;}};classB{public:voi

df(){cout<<"FromB"<<endl;}};classMI:publicA,publicB{public:voidg(){cout<<"FromMI"<<endl;}};voidmain(){MImi;mi.f();//L1:错误mi.A::f();//L2:正确}从MI的类

图可以看出,它有2个f()函数,因此在调用时,需要像L2语句那样指出调用函数出自的基类4.7.3多继承的构造函数与析构函数1.派生类必须负责为每个基类的构造函数提供初始化参数,构造的方法和原则与单继承相同。2.构造函数的调用次序仍然是先基类,再对象成员,然后才是派生类的

构造函数。3.基类构造函数的调用次序与它们在被继承时的声明次序相同,与它们在派生类构造函数的初始化列表中的次序没有关系。4.多继承方式下的析构函数调用次序仍然与构造函数的调用次序相反。【例4-17】类Base1、Base2、Base3、Derived的继承关系如图所示,

验证其构造函数和析构函数的调用次序。4.7.3多继承的构造函数与析构函数//Eg4-17.cpp#include<iostream>usingnamespacestd;classBase1{private:intx;public:Base1(inta=1){x

=a;cout<<"Base1constructorx="<<x<<endl;}~Base1(){cout<<"Base1destructor..."<<endl;}};4.7.3多继承的构造函数与析构函数classBase2{private:in

ty;public:Base2(inta){y=a;cout<<"Base2constructory="<<y<<endl;}~Base2(){cout<<"Base2destructor..."<<endl;}};classBase3{private:intz;public

:Base3(inta){z=a;cout<<"Base3constructorz="<<z<<endl;}~Base3(){cout<<"Base3destructor..."<<endl;}};classDerived:publicBase1,protecte

dBase2,privateBase3{private:inty;public:Derived(inta,intb,intc):Base3(b),Base2(a){y=c;cout<<"Derivedconstructory=“<<y<<endl;}~Derived(

){cout<<"Deriveddestructor...“<<endl;}};voidmain(){Derivedd(2,3,4);}4.7.3多继承的构造函数与析构函数本程序的运行结果如下:Base1constructorx=1Base2constructory

=2Base3constructorz=3Derivedconstructory=4Deriveddestructor...Base3destructor...Base2destructor...Base1destr

uctor...分析此结果的产生过程!1.引入的虚拟继承的原因派生类间接继承同一基类使得间接基类(Person)在派生类中有多份拷贝,引发二义性。stuEmployeeStudentPersonname…EmployeePersonname…4.8继拟继承•虚拟继承使基类

在派生类中只存在一份拷贝,解决了基类数据成员的二义性问题4.8继拟继承2.虚拟继承的定义方式class派生类名:virtual[继承方式]基类名1,virtual[继承方式]基类名2,…{派生类成员声明与定义;};•关键字virtual限定

继承方式,将基类指定为虚拟基类,就使该基类的成员在派生类中只有一份拷贝。•前面的stuEmployee类虚拟继承Person的形式如下:classStudent:virtualpublicPerson{……}//Person为虚基类classEmployee:virtua

lpublicPerson{……}//Person为虚基类classStuEmployee:publicStudent,publicEmployee{……}4.8继拟继承classA{public:voidvf(){cout<<"Icomefromclass

A"<<endl;}};classB:publicA{};classC:publicA{};classD:publicB,publicC{};voidmain(){Dd;d.vf();//error}ABCDVf()Vf()Vf()B.A::Vf()C.A::Vf()AVf(

)【例】类A是类B、C的基类,类D从类B、C继承,在类D中调用基类A的成员会产生二义性。classA{public:voidvf(){cout<<"IcomefromclassA"<<endl;}};classB:virtualpub

licA{};classC:virtualpublicA{};classD:publicB,publicC{};voidmain(){Dd;d.vf();//okay}ABCDVf()Vf()Vf()A::

Vf()将【例】改为虚拟继承不会产生二义性。3、虚拟继承的构造次序–若基类由虚基类派生而来,则派生类必须提供对间接基类的构造(即在构造函数初始列表中构造虚基类,无论此虚基类是直接还是间接基类)–虚基类的初始化与一般的多重继承的初始化在语法上是一样的,但构造函数的调用顺序不同;①先

调用虚基类的构造函数,再调用非虚基类的构造函数②若同一层次中包含多个虚基类,这些虚基类的构造函数按它们的继承次序调用③若虚基类由非基类派生而来,则仍然先调用基类构造函数,再调用派生类构造函数4.8继拟继承【例4-18】虚基类的执行次序分析。

//Eg4-18.cpp#include<iostream>usingnamespacestd;classA{inta;public:A(){cout<<"ConstructingA"<<endl;}};classB{public:B(){cout<<"Co

nstructingB"<<endl;}};4.8继拟继承classB1:virtualpublicB,virtualpublicA{public:B1(inti){cout<<"ConstructingB1"<<endl;}};classB2:publicA,virtu

alpublicB{public:B2(intj){cout<<"ConstructingB2"<<endl;}};classD:publicB1,publicB2{public:D(intm,intn)

:B1(m),B2(n){cout<<"ConstructingD“<<endl;}Aa;};voidmain(){Dd(1,2);}4.8继拟继承程序的运行结果如下:ConstructingBConstructingAConstructingB1Constru

ctingAConstructingB2ConstructingAConstructingD此运行结果表明:D的间接虚拟基类B只被构造了1次4、虚基类由最终派生类初始化–在没有虚拟继承的情况下,每个派生类的构造函数只负责其直接基类的初始化。

但在虚拟继承方式下,虚基类则由最终派生类的构造函数负责初始化。––在虚拟继承方式下,若最终派生类的构造函数没有明确调用虚基类的构造函数,编译器就会尝试调用虚基类不需要参数的构造函数(包括缺省、无参和缺省参数的构造函数),如果没

找到就会产生编译错误。4.8继拟继承【例4-19】类A是类B、C的虚基类,类ABC从B、C派生,是继承结构中的最终派生类,它必须负责虚基类A的初始化。//Eg4-19.cpp#include<iostream.h>classA{inta;public:A(intx){a=x;cou

t<<"VirtualBassA..."<<endl;}};4.8继拟继承5、成员函数冲突与优先级•如果虚基类和派生类中都有同名成员函数,仍然有可能会产生命名冲突。•假设类A具有函数f,类B和C都从A虚拟派生,类ABC继承了类B和类C,详见例4-19。对于ABC类对象的f成员函数调

用,存在以下几种情况:①如果类B、C和ABC都没有定义f函数,在类ABC的对象中只有1个来源于虚基类A中的f函数,没有冲突。但是,若B、C都不是虚拟继承于A;或者其中一个虚拟继承于A,另一个非虚拟继承

于A;则在ABC对象中的f函数有多个,会产生冲突。②如果类B、C中有一个定义了f函数,则在调用ABC对象的f函数时,B或C中的f函数具有优先权。例4-19中“obj.f()”的输出证明它调用的是类B中的f函数。③不论上面哪种情况,如果ABC类定义了f函数,当ABC的对象调用f函

数时,不会有冲突,ABC中的f函数具有优先权。4.8继拟继承classB:virtualpublicA{public:B(inti):A(i){cout<<"VirtualBassB..."<<endl;}};clas

sC:virtualpublicA{intx;public:C(inti):A(i){cout<<"ConstructingC..."<<endl;x=i;}};classABC:publicC,publicB{

public:ABC(inti,intj,intk):C(i),B(j),A(i)//L1,这里必须对A进行初始化{cout<<"ConstructingABC..."<<endl;}};voidmain(){ABCobj(1,2

,3);}程序的运行结果如下:VirtualBassA...ConstructingC...VirtualBassB...ConstructingABC...B派生类ABC必须为其祖先类A提供构造函数初始化列表,否则报编译错误

!5.7基类与派生类对象的关系•基类对象与派生类对象之间存在赋值相容性。包括以下几种情况:–把派生类对象赋值给基类对象。–把派生类对象的地址赋值给基类指针。–用派生类对象初始化基类对象的引用。•反之则不行,即不能把基

类对象赋值给派生类对象;不能把基类对象的地址赋值给派生类对象的指针;也不能把基类对象作为派生对象的引用。【例5-17】把派生类对象赋值给基类对象的例子。//CH5-17.cpp#include<iostream>using

namespacestd;classA{inta;public:voidsetA(intx){a=x;}intgetA(){returna;}};classB:publicA{intb;public:voidsetB(intx){b=x;}intgetB(){r

eturnb;}};voidf1(Aa,intx){a.setA(x);}voidf2(A*pA,intx){pA->setA(x);}voidf3(A&rA,intx){rA.setA(x);}voidmain(){Aa1,*pA;Bb1,*pB;a1.setA(1);b1.

setA(2);a1=b1;cout<<a1.getA()<<endl;cout<<b1.getA()<<endl;a1.setA(10);cout<<a1.getA()<<endl;cout<<b1.getA()<<endl;pA=&b1;pA->set

A(20);cout<<pA->getA()<<endl;cout<<b1.getA()<<endl;A&ra=b1;ra.setA(30);cout<<pA->getA()<<endl;cout<<b1.getA()<<endl;b

1.setA(7);cout<<b1.getA()<<endl;f1(b1,100);cout<<b1.getA()<<endl;f2(&b1,200);cout<<b1.getA()<<endl;f3(b1,300)

;cout<<b1.getA()<<endl;}5.7基类与派生类对象的关系•说明:•①不论以哪种方式把派生类对象赋值给基类对象,都只能访问到派生类对象中的基类子对象部份的成员,不能访问派生类的自定义成员。•②只能把派生类对象赋值给基类对象,不能把基类对象赋值给派生类对象。5.7

基类与派生类对象的关系4.9继承与组合1、继承与组合在OOP中的应用场景–继承与组合(也称合成)是C++实现代码重用的两种主要方法。通过继承,派生类可以获得基类的程序代码,从而达到代码重用的目的。而组合则体现了类之间的另一种关系,是指一个类可以包容另外的类,即用其他类来定义它的对象成员。2、

继承解决的主要问题–继承关系常被称为“Is-a”关系,即两个类之间若存在Is-a关系,就可以用继承来实现它。比如,水果和梨,水果和苹果,它们就具有Is-a关系。因为梨是水果,苹果也是水果,所以梨和苹果都可以从水果继承,获得所有水果都具有的通用特征。3、组合解决的主要问题–组

合常用于描述类之间的“Has-a”关系,即一个类拥有另外一些类。比如,图书馆有图书,汽车有发动机、车轮胎、座位等,计算机有CPU、存储器、显示器等,这些都可以用类的组合关系来实现。【例4-20】设计学生选学课程程序,要求能

够管理指定课程的选课学生名单。(1)问题分析–在本问题中,主要涉及的对象包括学生、课程,可以设计课程类,学生类分别管理课程信息和学生信息。–学生选修课程则和课程和学生都有关系,可以设计选修课程类来管理它。由于选修时需要知道选修的课程信息和学生名单。这一关系可以用类的包

含关系处理,即选修课程类的内部包含课程和学生类的对象。4.9继承与组合(2)数据抽象–用Coure表示课程类,包括课程编号、学分和课程名称,分别用courseName,cno和credit表示它们,并设计setCno/getCno,setCredit/getCred

it……来设置和读取数据成员的值,能够一次性设置全体数据成员值的setCourse成员函数和默认构造函数,以及显示成员值的display函数。–用Student表示学生类,分别用sno和stuName表示学号和姓名。用setSno/getSno……设置和读取sno和stu

Name的值,用display函数显示成员的值。–课程选修类是本题的核心类,用SelectCourse表示,用数据成员course、maxNum、curNum分别表示选学的课程、最多允许选课的人数、实际选课

人数,用指针成员stu存取选课学生名单,它指向Student类型的动态数组,该数组由构造函数分配。4.9继承与组合(3)数据抽象结果4.9继承与组合#include<iostream>#include<string>usingnamespacestd;classC

ourse{//编译器会为该类生成合成的拷贝构造函数和赋值运算符函数public:voidsetCno(intcNumber){cno=cNumber;}intgetCno(){returncno;}voi

dsetCredit(doublecrd){credit=cno;}doublegetCredit(){returncredit;}voidsetCname(stringcname){courseName=cname;}string

getCourseName(){returncourseName;}Course(intCno=0,doublecre=0,stringcName=""){setCourse(Cno,cre,cName);}voiddisplay(){cout<<"

课程号:"<<cno<<"\t课程名称:"<<courseName<<"\t学分:"<<credit<<endl;}voidsetCourse(intCno=0,doublecre=0,stringcName=""){cno=Cno;credit=cre;courseName=cName;}pri

vate:intcno;doublecredit;stringcourseName;}//student的程序代码,编译器会为该类合成默认的拷贝构造函数和赋值函数classStudent{public:voidsetSno(intSnumber){

sno=Snumber;}intgetSno(){returnsno;}voidsetStudentName(stringSname){stuName=Sname;}stringgetStudentName(){returnstuName;}Student(intSno=0

,stringSName=""){setStudent(Sno,SName);}voiddisplay(){cout<<"学号:"<<sno<<"\t姓名:"<<stuName<<endl;}voidsetStud

ent(intSno=0,stringSname=""){sno=Sno;stuName=Sname;}private:intsno;stringstuName;};4.9继承与组合classSelectCourse{private:intmaxNum=10,cur

Num=0;Coursecourse;Student*stu=nullptr;public:voidsetCourse(Coursec){course=c;}CoursegetCourse(){returncourse;}voi

dsetMaxNum(intn){maxNum=n;}intgetMaxNum(){returnmaxNum;}voidsetCurNum(intn){curNum=n;}intgetCurNum(){returncurNum;}Student*getStudent(){ret

urnstu;}voidsetStudent(Students[]){stu=s;}StudentgetAt(intn){returnstu[n];}voidappenStudent(Students){if(curNum<maxNum)stu[curNum++

]=s;}voiddisplay(){course.display();cout<<“最多选课人数:"<<maxNum<<"\t实选人数:"<<curNum<<endl;cout<<"选课学生名单:"<<endl;for(inti=0;i<curNum

;i++)stu[i].display();}SelectCourse(){stu=newStudent[maxNum];}SelectCourse(Coursec,intmNum,intcNum,Students[]):course(c),maxNum(mNum),cu

rNum(cNum),stu(newStudent[maxNum]){for(inti=0;i<cNum;i++)stu[i]=s[i];}SelectCourse(constSelectCourse&o):course(o.course),maxNum(o.maxNu

m),curNum(o.curNum){stu=newStudent[o.maxNum];for(inti=0;i<o.curNum;i++)stu[i]=o.stu[i];}SelectCourse&operator=(constSelectCourseo){course

=o.course;maxNum=o.maxNum;curNum=o.curNum;for(inti=0;i<o.curNum;i++)stu[i]=o.stu[i];return*this;}~SelectCourse(){delete[]stu;}};构造函数—析构函数设计voidm

ain(){//下面的代码段,测试SelectCourse类构造函数和显示函数的运行情况Coursecourse;course.setCourse(101,3.5,"C++面向对象程序设计");Students[2],s1;s[0].setStuden

t(10,"高大山");s[1].setStudent(11,"李明育");SelectCoursesc(course,10,2,s);cout<<"----------------------sc-----------------

-------"<<endl;sc.display();//下面的代码段测试SelectCourse类的拷贝构造函数和添加选课学生函数的运行情况SelectCoursesc2,sc1=sc;s1.setStudent(14,"黄始仁");sc1.ap

penStudent(s1);cout<<"----------------------sc1(sc)-------------------"<<endl;sc1.display();//下面的代码段测试SelectCourse类的赋值运算符函数的运行情况sc2=sc1;cout<<"------

---------------sc2=sc1------------------"<<endl;sc2.display();//下面的代码段测试SelectCourse类中获取学生名单和人数的成员函数的运行情况Student*sname=sc2.getStudent();c

out<<"--------------------sc2.getStudent()----------"<<endl;for(inti=0;i<sc2.getCurNum();i++)(sname++)->disp

lay();}4.10编程实例【例4-21】某校每位学生都要学习英语、语文、数学三门公共课程以及不同的专业课程。会计学专业要学习会计学和经济学两门课程,化学专业要学习有机化学和化学分析两门课程。编程序管理学生成绩,计算公共课的总分和平均分,以及所有

课程的总成绩。(1)问题分析–在问题描述中,由于英语、语文、数学三门公共课程是所有学生都要学习的,可以将它抽象成为一个基类comFinal,由它来管理这三门基础课程的成绩。另外两个专业则分别抽象成类Account和Chemistry,分别管理会计

学和化学两专业的课程成绩。–在整个问题域中还涉及学生,应该抽象出学生类Student来管理学生的档案。为简化问题,此处忽略了学生类的设计,仅用一个姓名都代表学生,并将此名字作为comFinal类的一个

数据成员。4.10编程实例(2)数据抽象–基类comFinal抽象①用name、english、chinese、math分别表示学生姓名、英语、语文和数学成绩,并为每个数据成员设计set/get成员函数以修改/读取其值②设计成员函数getTo

tal、getAverage、show,分别用于计算总分、平均分、输出学生的各科成绩。设计构造函数ComFinal为各数据成员提供初始化值。③由于设计了具有参数的构造函数,编译器就不会再为本类合成默认构造函数,为了便于ComFinal的继承及构造,以及本类无

参对象和数组的定义,重定义了本类的默认构造函数。4.10编程实例•会计学Account抽象①从ComFinal派生,继承基类的姓名及公共课处理能力,需要在构造函数初始化列表中对ComFinal类提供构造初值。②分别用数据成员account和e

con表示会计学和经济学两门主科,并为之设置set/get成员函数;③设计成员函数getMajTotal、getMajAvg和show,分别用来计算两主科的总分、平均分,以及显示输出学生的各项成绩数据。•化学类Chemistry抽象①用数据成员an

aly和chemistr表示化学化析和化学两门主科②按照与设计Account类相同的方法设计Chemistry类。•三个类的构造函数、析构函数和赋值运算符函数设计①由于三个类都没有指针成员,也没有在构

造函数中为任何数据成员分配动态存储空间,不必设计析构函数中进行动态存储空间的回收。②由于没有指针成员需要特别处理,也不必定义拷贝构造函数、赋值运算符函数和析构函数。编译器会为它们生成对应的默认合成函数,

这些合成函数能够正确完成对象的构造、拷贝、赋值和析构。(3)编程过程1.在项目中添加各类的空头文件和源码文件(1)在C盘建立目录C:\course,用于保存本程序中的所有文件。然后启动VisualC++2015,选择“新建”|“项目

”|“VisualC++”,在弹出的“新建项目”对话框中,指定“位置”为“c:\course”,在“名称”编辑框中输入“com_main”,单击“确定”。(2)在弹出的向导对话中,单击“下一步”,显示出“应用程序设置”对话框,取消“附加项”下面的“预编译头”和“安全开发生命周期(SDL)

检查”的复选设置。如图4-16所示。取消复选框中的“勾选”后单击“完成”按钮,进入VisualC++2015集成开发环境,如图4-17所示4.10编程实例(3)在“解决方案资源管理器”中,右击“头文件”|“添加”|“新建项”,

然后在弹出的对话框中选中“头文件”,并在名称中输入ComFinal.h。按照同样的方法将Account.h,Chemistry.h添加到项目中。(4)按照与添加头文件相同的方法,将各类的源文件ComFinal.cpp,Account.cpp,Chemistry.cpp添加到

项目中。项目的结构如图4-17所示,只不过到目前为此,这些头文件和源文件都是空文件。4.10编程实例4.10编程实例2.编写各类头文件和源文件的程序代码(1)建立ConFinal类。①在comFinal.h头文件中输入如下内容://comFinal.h#ifndefcomFi

nal_h#definecomFinal_hclasscomFinal{protected:charname[20];//学生姓名intenglish,chinese,math;//公共课成绩及总分4.10编程实

例public:comFinal(char*n,intEng,intChi,intMat);comFinal(){};char*getName(){returnname;}intgetEng(){returnenglish;}intge

tChi(){returnchinese;}intgetMat(){returnmath;}voidsetEng(intx){english=x;}voidsetChi(intx){chinese=x;}voidsetMat(

intx){math=x;}intgetTotal(){returnenglish+chinese+math;}doublegetAverage(){return(english+chinese+math)/3;}voidshow

();//显示学生各公共课的成绩、平均分和总分};#endif②在ComFinal.cpp源文件中输入如下内容://ComFinal.cpp#include<iostream>#include"comFinal.h"

usingnamespacestd;comFinal::comFinal(char*n,intEng,intChi,intMat){english=Eng;chinese=Chi;math=Mat;strcpy(name,n);}voidcomFinal::show(){cout<<

"学生姓名:"<<getName()<<endl;cout<<"英语成绩:"<<getEng()<<endl;cout<<"语文成绩:"<<getChi()<<endl;cout<<"数学成绩:"<<getMat()<<e

ndl;cout<<"基础课总分:"<<getTotal()<<endl;cout<<"基础课平均成绩:"<<getAverage()<<endl<<endl;}①在Account.h头文件中输入如下内容://Account.h#include"comFinal

.h"#ifndefAccount_h#defineAccount_hclassAccount:publiccomFinal{protected:intaccount;//会计学成绩intecon;//经济学成绩(2)Account类的建立4.10编程实例pub

lic:Account(char*n,intEng,intChi,intMat,intAcc,intEco);Account(){};intgetMajtotal(){returnecon+account;}floatgetMajave(){returnfloat((account+e

con)/2);}intgetAccount(){returnaccount;};intgetEcon(){returnaccount;}voidsetAccount(intx){account=x;}voidsetEcon(intx){econ

=x;}voidshow();};#endif②在Account.cpp源文件输入如下内容://Account.cpp#include"account.h"#include<iostream>usingnamespacestd;Account::Ac

count(char*n,intEng,intChi,intMat,intAcc,intEco):comFinal(n,Eng,Chi,Mat){econ=Eco;account=Acc;}voidAccount::show(){comFinal::show();cout<<"会计学成绩:

"<<account<<endl;cout<<"经济学成绩:"<<econ<<endl;cout<<"总分"<<getTotal()+account+econ<<endl;}(3)建立Chemistry类①在Chemistry.h

头文件中输入如下内容://Chemistry.h#include"comFinal.h"#ifndefchemistry_h#definechemistry_hclassChemistry:publicc

omFinal{protected:intchemistr;//化学成绩intanaly;//化学分析成绩(3)建立Chemistry类public:Chemistry(char*n,intEng,in

tChi,intMat,intChem,intAnal);Chemistry(){};intgetMajtotal(){returnanaly+chemistr;}floatgetMajave(){returnfloat((chemistr+analy)/2);}intgetChe()

{returnchemistr;};intgetAnl(){returnanaly;}voidsetChe(intx){chemistr=x;}voidsetAnl(intx){analy=x;}voidshow();};#endif②在Chemistry.cpp源文件中输入如下内容:#incl

ude"stdafx.h"//Chemistry.cpp#include<iostream>#include"Chemistry.h"usingnamespacestd;Chemistry::Chemistry(char*n,intEng,intChi,intMat

,intChem,intAnal):comFinal(n,Eng,Chi,Mat){chemistr=Chem;analy=Anal;}voidChemistry::show(){comFinal::show();cout<<"

有机化学:"<<chemistr<<endl;cout<<"化学分析:"<<analy<<endl;cout<<"总分"<<getTotal()+chemistr+analy<<endl;}(4)建立主程序并运行程序在com_main.cpp中,输入

下面的程序代码://com_main.cpp#include"Chemistry.h"#include"Account.h"#include<iostream>usingnamespacestd;vo

idmain(){Accounta1("张三星",98,78,97,67,87);Chemistryc1("光红顺",89,76,34,56,78);a1.show();cout<<"-----------------------

------------"<<endl;c1.setAnl(100);c1.show();}编译并运行该程序,输出结果如下:学生姓名:张三星英语成绩:98语文成绩:78数学成绩:97基础课总分:273基础课平均成绩:91会计学成绩

:67经济学成绩:87总分427-----------------------------------学生姓名:光红顺英语成绩:89语文成绩:76数学成绩:34基础课总分:199基础课平均成绩:66有机化学:56化学分析:100总分355

小橙橙
小橙橙
文档分享,欢迎浏览!
  • 文档 25747
  • 被下载 7
  • 被收藏 0
广告代码123
若发现您的权益受到侵害,请立即联系客服,我们会尽快为您处理。侵权客服QQ:395972555 (支持时间:9:00-21:00) 公众号
Powered by 太赞文库
×
确认删除?