C 面向对象程序设计(第二版)课件 第5章继承_第1页
C 面向对象程序设计(第二版)课件 第5章继承_第2页
C 面向对象程序设计(第二版)课件 第5章继承_第3页
C 面向对象程序设计(第二版)课件 第5章继承_第4页
C 面向对象程序设计(第二版)课件 第5章继承_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象程序设计第5章继承主要内容●5.1继承与派生●5.2派生类的访问控制●

5.3派生类的构造函数和析构函数●5.4多继承●

5.5虚基类●5.6赋值兼容性规则5.1继承与派生01继承与派生概念建筑工业建筑单层厂房多层厂房民用建筑住宅平房多层高层商场医院学校地下建筑人防工程地下车库继承和派生的概念源于人们对客观世界的认识过程是自然界普遍存在的一种现象。许多事物之间存在着继承的关系。上层:基类;下层:派生类。图5.1建筑类的继承与派生关系抽象程度最高,具有一般意义的概念具体化、特殊化抽象化01

继承与派生的概念classPersonclassStudentPropertiesNameGenderCityOperationsSpeak()GetInformation()PropertiesSNOSchoolNameGenderCityOperationsHaveLesson()Speak()GetInformation()那个具有一般性?哪个更具有特殊性、更具体?02继承的种类每一个派生类有且仅有一个基类,派生类可以看作是基类的特例单继承:多继承:一个派生类可以有多个基类,它继承了多个基类的特性。图5.2单继承、多继承和多层继承以派生类作为基类再派生出派生类。多级继承:02继承的种类(1)继承关系可以是多级的,即可以有类Y继承类X和类Z继承类Y同时存在。(2)不允许循环继承。(3)基类中能够被继承的部分只能是公有成员和保护成员,私有成员不能被继承。C++的继承关系有以下几个特点03派生类的声明class<派生类名>:<继承方式><基类名>{<派生类新定义成员>;};说明:(1)派生类定义中,继承方式只限定紧跟其后的那个基类。如果不显式给出继承方式,系统默认为私有继承。(2)派生方式关键字为private、public和protected。缺省的继承方式是私有继承。继承方式规定了派生类成员和类外对象访问基类成员的权限。(3)派生类新定义的成员是指继承过程中新增加的数据成员和成员函数。

单继承的定义03派生类的声明

多继承的定义格式如下:说明:(1)每一个继承方式对应的是紧接其后给出的基类。(2)必须给每个基类指定一种此派生类从这个基类继承的继承方式,(3)如果缺省,相应的继承方式则取私有继承,而不是和前一个基类取相同的继承方式。class派生类名:继承方式1基类名1,继承方式2基类名2,…{private:

派生类的私有数据和函数

public:

派生类的公有数据和函数

protected:

派生类的保护数据和函数};03派生类的声明【例5-1】在普通的时钟类Clock基础上派生出闹钟类AlarmClock。classAlarmClock:

publicClock{private:intAH,AM;//响铃的时间boolOpenAlarm;//是否关闭闹钟public:SetAlarm(intAH,intAM);//设置响铃时间SwitchAlarm(boolOpen=true);//打开/关闭闹铃ShowTime();//显示当前时间与闹铃时间};派生类AlarmClock的成员构成图类名成员名AlarmClock::Clock::H,M,SSetTime()ShowTime()AH,AM,OpenAlarmSetAlarm()SwitchAlarm()ShowTime()#include<iostream>usingnamespacestd;classClock{private:intH,M,S;public:voidSetTime(intH=0,intM=0,intS=0);voidShowTime();Clock(intH=0,intM=0,intS=0);~Clock();};03派生类的声明派生类的生成过程:(1)吸收基类成员

基类的全部成员被派生类继承,作为派生类成员的一部分。(2)改造基类成员

派生类根据实际情况对继承自基类的某些成员进行限制和改造。(同名覆盖:如ShowTime())(3)添加新成员

派生类在继承基类成员的基础之上,根据派生类的实际需要,增加一些新的数据成员和函数成员,以描述某些新的属性和行为。未被继承的部分

(private)

被继承的部分

(public,protected)

被继承的部分

新增加或改变的部分基类派生类总结5.2派生类的访问控制1301公有继承方式三种继承方式:公有继承:public私有继承:private保护继承:protect1..派生类的继承方式14不同继承方式的影响主要体现在:派生类成员对基类成员的访问权限通过派生类对象对基类成员的访问权限01公有继承方式继承不影响基类的数据成员分配,不破坏基类的独立性;所有的数据成员都占有内存,静态数据成员被唯一地放置在全局数据区。派生类对象占有的内存是基类对象占有的内存和派生类新增数据成员的内存之和。类层次本身的访问控制属性和继承方式对于对象的内存分配无影响。几个主要的概念:1501公有继承方式

(1)

公有继承:public基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。通过派生类的对象只能访问基类的public成员。1601

公有继承方式【例5-2】用学生档案类Student派生学生的成绩类Score

#include<iostream>usingnamespacestd;classStudent{private:intNo;

protected:intAge;

voidSetNo(intno){this->No=no;}

voidSetAge(intage){this->Age=age;}

voidSetSex(charsex){this->Sex=sex;}

public:charSex;

intGetNo()const{returnNo;}

intGetAge()const{returnAge;}

charGetSex()const{returnSex;}

voidShow()

{cout<<"No="<<No<<'\t'<<"Age="<<Age<<'\t'<<"Sex="<<Sex<<endl;}voidSetStudent(intno,intage,charsex){

SetNo(no);SetAge(age);SetSex(sex); }};classScore:publicStudent {private:intPhi,Math; public:intSetPhi(intPhi){returnthis->Phi=Phi;}intSetMath(intMath){returnthis->Math=Math;}voidShow(){cout<<"No="<<GetNo()<<'\t'<<"Age="<<Age<<'\t'<<"Sex="<<Sex<<'\t'<<"Phi="<<Phi<<'\t'<<"Math="<<Math<<endl;}};5-1.mp41701公有继承方式intmain(){Scores;s.SetStudent(101,20,'M');s.SetPhi(90);s.SetMath(80);s.Show();cout<<"No="<<s.GetNo()<<'\t'<<"Age="<<s.GetAge()

<<'\t’<<"Sex="<<s.Sex<<endl;return0;}【例5-2】续No=101Age=20Sex=MPhi=90Math=80No=101Age=20Sex=M运行结果:5-1.mp41802私有继承方式私有继承(private)基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问。派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。通过派生类的对象不能直接访问基类中的任何成员。如何在私有继承下访问基类的公有成员思考一下:1902私有继承方式#include<iostream>usingnamespacestd;classBase{public:ints;Base(){s1=1;s2=2;s3=3;s=4;}voidfun(){cout<<s<<endl;//正确

cout<<s1<<endl;//正确

cout<<s2<<endl;//正确

cout<<s3<<endl;//正确

}public:ints1;protected:ints2;private:ints3;};classDerived:privateBase{public:ints;Derived(inti){Base();s=i;}voidfun(){cout<<s<<endl;//正确,public成员。

cout<<s1<<endl;//正确,基类public成员,在派生类中变成了private,可以被派生类访问。

cout<<s2<<endl;//正确,基类的protected成员,在派生类中变成了private,可以被派生类访问。

//cout<<s3<<endl;//错误,基类的private成员不能被派生类访问。

}};intmain(){Derivedp(50);cout<<p.s<<endl;//正确。public成员

//cout<<p.s1<<endl;//错误,private成员不能在类外访问。

//cout<<p.s2<<endl;//错误,private成员不能在类外访问。

//cout<<p.s3<<endl;//错误,private成员不能在类外访问。

return0;205-2.mp403保护继承方式基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问。派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。通过派生类的对象不能直接访问基类中的任何成员保护继承:protected2103

保护继承方式【例5-4】保护继承。#include<iostream>usingnamespacestd;classPoint{public:voidfun1()

{

cout<<"Point类中的公有成员函数"<<endl;

}protected:voidfun2(){

cout<<"Point类中的保护成员函数"<<endl;}};classRectangle:protectedPoint{ //Rectangle新添加的成员};classSquare:publicRectangle{public:voidfun(){

fun1();

fun2();}};intmain(){Squares;s.fun();return0;}Point类中的公有成员函数Point类中的保护成员函数225-3.mp4派生类的访问控制总结表5-1不同继承方式的基类和派生类特性235.3派生类的构造和析构函数2401派生类的构造函数派生类中需要声明自己的构造函数。声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。派生类的构造函数需要给基类的构造函数传递参数1、派生类的构造函数2、派生类构造函数的定义派生类名(参数总表):基类名1(参数表1),…,基类名m(参数表m),

成员对象名1(成员对象参数表1),…,成员对象名n(成员对象参数表n)

{

派生类新增成员的初始化;

}基类成员的初始化表3、派生类构造函数调用规则单继承时,派生类构造函数调用的一般次序如下:

(1)调用基类构造函数。(2)调用内嵌成员对象的构造函数,调用顺序取决于它们在类中定义的顺序。(3)派生类自己的构造函数。

派生类构造函数的总参数表中定义的参数个数等于基类中构造函数参数的个数和派生类中新增数据成员的个数之和,新增成员包括普通数据成员和子对象数据成员。2501派生类的构造函数【例5-5】单继承机制下构造函数的调用顺序。#include<iostream>usingnamespacestd;classBaseclass{

public:Baseclass(inti)

{

a=i;cout<<"constructingBaseclassa="<<a<<endl;

}

private:inta; };classDerivedclass:publicBaseclass{public:

Derivedclass(inti,intj);private:intb;};Derivedclass::Derivedclass(inti,intj):Baseclass(i){

b=j;cout<<"constructingDerivedclassb="<<b<<endl;}intmain(){Derivedclassx(5,6);

return0;}constructingBaseclassa=5constructingDerivedclassb=6程序输出结果为:2602派生类的组合当一个类既是派生类同时又包含嵌入对象时,构造函数调用次序:调用基类的构造函数按类声明中嵌入对象出现的次序调用嵌入对象的构造函数执行派生类的构造函数通过初始化列表向嵌入对象的构造函数传递参数。组合的概念:一个类中的数据成员包含另一个类的对象。2702派生类的组合【例5-6】包括派生类新增的数据中有成员对象时,其构造函数的调用顺序。#include<iostream>usingnamespacestd;classBase1//基类{public:Base1(inti){a=i;cout<<"constructingBase1a="<<a<<endl;}private:inta; };classBase2{public:Base2(inti){b=i;cout<<"constructingBase2b="<<b<<endl;}private:intb; };classBase3{public:Base3(inti){c=i; cout<<"constructingBase3c="<<c<<endl;}private:intc; };classDerivedclass:publicBase1{public:Derivedclass(inti,intj,intk,intm);private:intd;Base2f;Base3g;};2802派生类的组合Derivedclass::Derivedclass(inti,intj,intk,intm):Base1(i),g(j),f(k){d=m;cout<<"constructingDerivedclassd="<<d<<endl;}intmain(){Derivedclassx(5,6,7,8);return0;}constructingBase1a=5constructingBase2b=7constructingBase3c=6constructingDerivedclassd=8Pressanykeytocontinue注意:成员对象f和g的调用顺序取决于它们有派生类中被声明的顺序,与它们在成员初始化列表中的顺序无关。程序运行结果:2903

派生类析构函数派生类的析构函数的功能是在该类对象消亡之前进行一些必要的清理工作。析构函数也不被继承,派生类自行声明声明方法与一般(无继承关系时)类的析构函数相同。不需要显式地调用基类的析构函数,系统会自动隐式调用。析构函数的调用次序与构造函数相反。(1)首先调用派生类的析构函数(清理派生类新增成员);(2)如果派生类中有成员对象,再调用派生类中成员对象所在类的析构函数(清理派生类新增的成员对象);(3)再调用普通基类的析构函数(清理从基类继承来的基类对象);(4)最后调用虚基类的析构函数。(虚基类的知识在后面讲解)析构函数调用规则如下:析构函数执行顺序和构造函数正好相反先自己(派生类本身)再客人(内嵌对象)后祖先(基类)。回想一下构造函数的调用次序?3003

派生类析构函数【例5-8】派生类析构函数的调用顺序。#include<iostream>usingnamespacestd;classBase1//基类{public:Base1(inti){

a=i;cout<<“constructingBase1a=”<<a<<endl;

}

~Base1(){

cout<<"destructingBase1"<<endl; } private:inta;};classBase2//基类Base2{public:Base2(inti)//构造函数 { b=i;cout<<"constructingBase2b="<<b<<endl; } ~Base2()//析构函数 {cout<<"destructingBase2"<<endl; }private:intb; };315-4.mp403

派生类析构函数classBase3//子对象f所属类{public:Base3(inti)//构造函数 { c=i;cout<<"constructingBase3c="<<c<<endl; } ~Base3()//析构函数 { cout<<"destructingBase3"<<endl; }private:intc;};classBase4//子对象g所属类{public:Base4(inti)//构造函数 { d=i;cout<<"constructingBase4d="<<d<<endl; } ~Base4()//析构函数 { cout<<"destructingBase4"<<endl; }private:intd;};3203

派生类析构函数classDerivedclass:publicBase1,publicBase2{public:Derivedclass(inti,intj,intk,intm,intn);~Derivedclass();private:inte;

Base3f;Base4g;

};Derivedclass::Derivedclass(inti,intj,intk,intm,intn):Base1(i),Base2(j),f(k),g(m){

e=n;cout<<"constructingDerivedclasse="<<e<<endl;}Derivedclass::~Derivedclass()//派生类析构函数{cout<<"destructingDerivedclass"<<endl;}contructingBase1a=5contructingBase2b=6contructingBase3c=7contructingBase4d=8contructingDerivedclasse=9destructingDerivedclassdestructingBase4destructingBase3destructingBase2destructingBase1intmain(){Derivedclassx(5,6,7,8,9); return0;}335.4多

承01多继承的概念在现实生活中,很多继承均表现为多重继承。一个派生类可以有多个直接基类,则这种继承方式称为多继承,或称为多重继承派生类与每一个基类之间关系可以看作是一个单继承。多继承时派生类的声明class派生类名:继承方式1基类名1,

继承方式2基类名2,...{

成员声明;}注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。02多继承构造函数和析构函数的调用次序多继承的构造函数调用顺序派生类的构造函数须同时负责该派生类所有基类构造函数的调用。构造函数调用顺序是:先调用所有基类的构造函数,再调用派生类的构造函数。处于同一层次的各基类构造函数的调用顺序取决于定义派生类所指定的基类顺序,与派生类构造函数中所定义的成员初始化列表顺序无关。多继承的析构函数的调用顺序执行派生类的析构函数;按照子对象声明的相反顺序依次调用子对象的析构函数;按照基类声明的相反顺序依次调用各基类的析构函数03多继承应用举例【例5-7】多继承方式下构造函数的调用顺序。#include<iostream>usingnamespacestd;classBase1{public:Base1(inti){a=i;cout<<"constructingBase1a="<<a<<endl;}private:inta; };classBase2{public:Base2(inti){ b=i;cout<<"constructingBase2b="<<b<<endl; }private:intb; };classDerivedclass:publicBase1,publicBase2{public:Derivedclass(inti,intj,intk);private:intc; };Derivedclass::Derivedclass(inti,intj,intc):Base2(i),Base1(j){this->c=c;cout<<"constructingDerivedclassc="<<c<<endl;}intmain(){Derivedclassx(5,6,7); return0;}constructingBase1a=6constructingBase2b=5constructingDerivedclassc=7Pressanykeytocontinue程序运行结果:04二义性问题在多重继承中,派生类对基类成员访问在下列两种情况下可能出现二义性。访问不同基类的相同名成员时可能出现二义性访问共同基类中成员时可能出现二义性多重继承可以反映现实生活中的情况,能够有效地处理一些较复杂的问题,使编写程序具有灵活性,充分体现了软件重用的优点,但是由于多重继承方式下,派生类可能有多个直接基类或间接基类,可能造成对基类中某个成员的访问出现了不确定的情况,使得这种访问具有二义性。g(),j()f()A类B1类g(),h()B2类D类g(),p()如果声明:Dc1;调用哪个函数时会产生二义性的问题?AB1B2D则c1.f();具有二义性而c1.g();无二义性(同名隐藏)04二义性问题bb1bb2dBase类成员Base类成员Base1类成员Base2类成员Derived类对象各类包含的数据成员04二义性问题。二义性问题解决方法(1)成员名限定

通过类的作用域限定符(::)明确限定出现二义性的成员是继承自哪一个基类。(2)成员重定义

在派生类中新增一个与基类中成员相同的成员,由于同名覆盖,程序将自动选择派生类新增的成员。一是使用作用域运算符。二是将直接基类的共同基类设置为虚基类。直接基类名::数据成员名直接基类名::成员函数名(参数表)04二异性问题【例5-9】派生类覆盖基类中的同名成员#include<iostream>usingnamespacestd;classBase1{ public:

inti;

voidfun(){cout<<"Base1的fun函数被调用"<<endl;}};classBase2{ public:

inti;

voidfun(){cout<<"Base2的fun函数被调用"<<endl;}};classDerived:publicBase1,publicBase2{ public:

inti;

voidfun(){cout<<"Derived的fun函数被调用"<<endl;}};intmain(){

Derivedd;

Derived*p=&d;

d.i=1;

d.fun();

return0;}同名数据成员同名函数成员程序运行结果:Derived的fun函数被调用应用举例【例】多重继承派生类构造函数和析构函数#include<iostream>usingnamespacestd;classBase1 {public:

Base1(inti){cout<<"constructingBase1"<<i<<endl;}

~Base1(){cout<<"destructingBase1"<<endl;}};classBase2 {public:Base2(intj){cout<<"constructingBase2"<<j<<endl;}~Base2(){cout<<"destructingBase2"<<endl;} };classBase3{public:Base3(){cout<<"constructingBase3*"<<endl;}~Base3(){cout<<"destructingBase3"<<endl;} };classDerived:publicBase2,publicBase1,publicBase3 {public:Derived(inta,intb,intc,intd):Base1(a),memberA2(d),memberA1(c),Base2(b){}private:Base1memberA1;Base2memberA2;Base3memberA3;};intmain(){Derivedobj(1,2,3,4);return0;}constructingBase22constructingBase11constructingBase3*constructingBase13constructingBase24constructingBase3*destructingBase3destructingBase2destructingBase1destructingBase3destructingBase1destructingBase2应用举例#include<iostream>usingnamespacestd;classB1{protected:intb1,m;public:B1(){b1=3;m=4;}};classB2{protected:intb2,m;public:B2(){b2=5;m=6;}};classD:publicB1,publicB2{intd,m;public:D(){d=7;m=8;}voidshow(){cout<<B1::b1<<"\t"<<b2<<"\t"<<d<<'\n';cout<<B1::m<<"\t"<<B2::m<<"\t"<<m<<'\n';}};intmain(){Dtest;test.show();return0;}3

574

6

8testB1::b1=3B1::m=4B2::b2=5B2::m=6D::d=7D::m=8程序运行结果5.5虚

类01虚基类的概念基类Aa派生类BA::a派生类CA::a派生类DA::aA::a派生类DA::avirtualvirtual在多重派生的过程中,共同一个基类经过多级继承后会出现用“类名::”无法解决的二义性问题。欲使共同的基类在派生中只有一个拷贝,可将此基类说明成虚基类。虚基类把从不同的路径继承过来的虚基类的派生成员在派生类中只出现一次。02虚基类的声明方法声明虚基类的方法是,定义派生类时,在基类名称的前面加上关键字virtual。class派生类名:virtual

派生方式基类名{新增成员列表}或class派生类名:派生方式virtual

基类名{新增成员列表}关键字virtual可以放在派生方式的前面,也可以放在派生方式的后面。需要注意的是在第一级继承时就要将共同基类设置为虚基类03虚基类的举例【例5-14】定义虚基类,使派生类中只有基类的一个拷贝#include<iostream>usingnamespacestd;classBase0{public: intvar0; Base0(inta=0){var0=a;}};classBase1:virtualpublicBase0{public: //新增外部接口

intvar1; Base1(inta,intb):Base0(a){var1=b;}};classBase2:virtualpublicBase0{//定义派生类Base2public: //新增外部接口

intvar2; Base2(inta,intc):Base0(a){var2=c;}};classDerived:publicBase1,publicBase2{public: //新增外部接口

intvar;Derived(inta,intb,intc,intd):Base1(a,b),Base2(a,c) {var=d; }};intmain(){ Derivedd(2,4,6,8); cout<<"var0="<<d.var0<<endl;d.var0=10;cout<<"通过对象在d将var0设置成"<<d.var0<<endl;cout<<"通过Base2类访问var0,var0="<<d.Base2::var0<<endl;cout<<"通过Base1类访问var0,var0="<<d.Base1::var0<<endl;return0;}var0=0通过对象在d将var0设置成10通过Base2类访问var0,var0=10通过Base1类访问var0,var0=10直接访问var05-5.mp4

(3)类Derived中只有一个虚基类Base0,所以在执行类Base1和类Base2的构造函数时都不调用虚基类的构造函数,而是在类Derived中直接调用虚基类Base0的缺省的构造函数:Base0(inta=0){var0=a;},所以var0=0,若将构造函数改为:Base0(inta){x=a;},则编译时会发生错误。若将类Derived的构造函数改为:(1)改变成员var0值时,由基类Base1和Base2访问得到的var0值是相同的。(2)由虚基类派生出的派生类,必须在其构造函数的成员初始化列

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论