类的继承和派生课件_第1页
类的继承和派生课件_第2页
类的继承和派生课件_第3页
类的继承和派生课件_第4页
类的继承和派生课件_第5页
已阅读5页,还剩106页未读 继续免费阅读

下载本文档

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

文档简介

学校在册人员

学生

教职工第八章继承与多态本科生研究生硕士生博士生教师

行政人员

工人学校在学生教职工第八章继承与多态本科生研究生硕【例8.1】由在册人员类公有派生学生类classPerson{ stringIdPerson; //身份证号,18位数字

stringName; //姓名

boolSex; //性别

int Birthday; //格式1986年8月18日写作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string);//构造函数

Person(); //默认的构造函数

~Person(); //析构函数【例8.1】由在册人员类公有派生学生类public:【例8.1】由在册人员类公有派生学生类voidSetName(string);

//修改名字

stringGetName(){returnName;}

//提取名字

voidSetSex(Tsexsex){Sex=sex;} //修改性别

boolGetSex(){returnSex;} //提取性别

voidSetId(stringid){IdPerson=id;} //修改身份证号

stringGetId(){returnIdPerson;}

//提取身份证号

voidSetBirth(intbirthday){Birthday=birthday;}//修改生日

int GetBirth(){returnBirthday;}//提取生日

voidSetHomeAdd(string); //修改住址

stringGetHomeAdd(){returnHomeAddress;}

//提取住址

voidPrintInfo(); //打印个人信息};//接口函数:【例8.1】由在册人员类公有派生学生类voidSetN继承(inheritance):该机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。第八章继承与多态多态性(polymorphism):多态性是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性。以虚函数为基础的运行时的多态性是面向对象程序设计的标志性特征。体现了类推和比喻的思想方法。

继承(inheritance):第八章继承与多态多态性【例8.1】由在册人员类公有派生学生类classPerson{ stringIdPerson; //身份证号,18位数字

stringName; //姓名

boolSex; //性别

int Birthday; //格式1986年8月18日写作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string);//构造函数

Person(); //默认的构造函数

~Person(); //析构函数【例8.1】由在册人员类公有派生学生类public:【例8.1】由在册人员类公有派生学生类voidSetName(string);

//修改名字

stringGetName(){returnName;}

//提取名字

voidSetSex(Tsexsex){Sex=sex;} //修改性别

TsexGetSex(){returnSex;} //提取性别

voidSetId(stringid){IdPerson=id;} //修改身份证号

stringGetId(){returnIdPerson;}

//提取身份证号

voidSetBirth(intbirthday){Birthday=birthday;}//修改生日

int GetBirth(){returnBirthday;}//提取生日

voidSetHomeAdd(string); //修改住址

stringGetHomeAdd(){returnHomeAddress;}

//提取住址

virtualvoidPrintInfo(); //打印个人信息};//接口函数:【例8.1】由在册人员类公有派生学生类voidSetN【例8.1】由在册人员类公有派生学生类派生的学生类:classStudent:publicPerson //定义派生的学生类{stringNoStudent;//学号

coursecs[30];//30门课程与成绩public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);

//注意派生类构造函数声明方式

Student(); //默认派生类构造函数

~Student(); //派生类析构函数

SetCourse(string,int); //课程设置

intGetCourse(string); //查找成绩

voidPrintInfo(); //打印学生情况};structcourse{stringcoursename;

intgrade;};【例8.1】由在册人员类公有派生学生类派生的学生类:clas第八章继承与多态8.1继承与派生的概念

8.4虚基类(选读)

8.3多重继承与派生类成员标识

8.6多态性与虚函数

8.5派生类应用讨论

8.2派生类的构造函数与析构函数

第八章继承与多态8.1继承与派生的概念8.1

继承与派生的概念

层次概念是计算机的重要概念。通过继承(inheritance)的机制可对类(class)分层,提供类型/子类型的关系。

C++通过类派生(classderivation)的机制来支持继承。被继承的类称为基类(baseclass)或超类(superclass),新的类为派生类(derivedclass)或子类(subclass)。基类和派生类的集合称作类继承层次结构(hierarchy)。

如果基类和派生类共享相同的公有接口,则派生类被称作基类的子类型(subtype)。

层次概念:派生反映了事物之间的联系,事物的共性与个性之间的关系。派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程。8.1继承与派生的概念层次概念是计算机的重要概念。通8.1

继承与派生的概念8.1.1类的派生与继承

8.1.2公有派生与私有派生

8.1继承与派生的概念8.1.1类的派生与继承【例8.1】由在册人员类公有派生学生类派生的学生类:classStudent:publicPerson //定义派生的学生类{stringNoStudent;//学号

coursecs[30];//30门课程与成绩public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);

//注意派生类构造函数声明方式

Student(); //默认派生类构造函数

~Student(); //派生类析构函数

SetCourse(string,int); //课程设置

intGetCourse(string); //查找成绩

voidPrintInfo(); //打印学生情况};structcourse{stringcoursename;

intgrade;};【例8.1】由在册人员类公有派生学生类派生的学生类:clas派生类的定义:class派生类名:访问限定符基类名1《,访问限定符基类名2,……,访问限定符基类名n》{《《private:》

成员表1;》

//派生类增加或替代的私有成员《public:

成员表2;》

//派生类增加或替代的公有成员《protected:

成员表3;》

//派生类增加或替代的保护成员};//分号不可少其中基类1,基类2,……是已声明的类。在派生类定义的类体中给出的成员称为派生类成员,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。8.1.1类的派生与继承派生类的定义:8.1.1类的派生与继承基类1基类2……基类n派生类1派生类2基类派生类1派生类2(a)多重继承

(b)单继承

图8.1多重继承与单继承

一个基类可以直接派生出多个派生类

派生类可以由多个基类共同派生出来,称多重继承。8.1.1类的派生与继承多重继承:如果一个派生类可以同时有多个基类,称为多重继承(multiple-inheritance),这时的派生类同时得到了多个已有类的特征。单继承:派生类只有一个直接基类的情况称为单继承(single-inheritance)。基类1基类2……基类n派生类1派生类2基类派生类1派生类2(8.1.1类的派生与继承

在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。直接参与派生出某类称为直接基类,而基类的基类,以及更深层的基类称为间接基类。类族:

同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的类族。如MFC就是这样的族类,它由一个CObject类派生出200个MFC类中的绝大多数。多层次继承:8.1.1类的派生与继承在派生过程中,派编制派生类时可分四步

吸收基类的成员

改造基类成员

发展新成员

重写构造函数与析构函数

8.1.1类的派生与继承不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收

声明一个和某基类成员同名的新成员,派生类中的新成员就屏蔽了基类同名成员称为同名覆盖(override)派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。派生编程步骤:编制派生类时可分四步吸收基类的成员改造基类成员发展新成8.1.1类的派生与继承第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。第三步中,独有的新成员才是继承与派生的核心特征。第四步是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用一律重写可免出错(?????)。访问控制:亦称为继承方式,是对基类成员进一步的限制。访问控制也是三种:公有(public)方式,亦称公有继承保护(protected)方式,亦称保护继承私有(private)方式,亦称私有继承。

8.1.1类的派生与继承第二步中,新成员如是成员函数,参8.1.2公有派生与私有派生访问限定符两方面含义:派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作),和从派生类对象之外对派生类对象中的基类成员的访问。classStudent:publicPerson{……public: Student(stringid,stringname,Tsexsex,int birthday,stringhomeadd,stringnostud);};classPerson{private:

stringName; //姓名public: stringGetName(){returnName;} //提取名字 ……}

8.1.2公有派生与私有派生访问限定符两方面含义:派生类成8.1.2公有派生与私有派生不可直接访问

不可直接访问

private不可直接访问

privateprotected不可直接访问

privatepublic私有派生

不可直接访问

不可直接访问

private不可直接访问

protectedprotected可直接访问

publicpublic公有派生

在派生类对象外访问派生类对象的基类成员

在派生类中对基类成员的访问限定

基类中的访问限定

派生方式

公有派生是绝对主流。8.1.2公有派生与私有派生不可直接访问不可直接访问p【例8.1】由在册人员类公有派生学生类//注意Person参数表不用类型Student::Student(stringid,stringname,Tsexsex,intbirthday,stringhomeadd,stringnostud):Person(id,name,sex,birthday,homeadd){ NoStudent=nostud;

for(inti=0;i<30;i++) //课程与成绩清空

{ cs[i].coursename="#"; cs[i].grade=0; }}派生类构造函数:【例8.1】由在册人员类公有派生学生类//注意Person参派生类构造函数的定义:派生类名::派生类名(参数总表):基类名1(参数名表1)《,基类名2(参数名表2),……,基类名n(参数名表n)》,《成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m)》{…… //派生类新增成员的初始化;} //所列出的成员对象名全部为新增成员对象的名字注意:在构造函数的声明中,冒号及冒号以后部分必须略去。

所谓不能继承并不是不能利用,而是把基类的构造函数作为新的构造函数的一部分,或者讲调用基类的构造函数。基类名仅指直接基类,写了底层基类,编译器认为出错。

冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。

8.2派生类的构造函数与析构函数派生类构造函数的定义:注意:8.2派生类的构造函数与析构派生类构造函数各部分执行次序:

1.调用基类构造函数,按它们在派生类定义的先后顺序,顺序调用。

2.调用成员对象的构造函数,按它们在类定义中声明的先后顺序,顺序调用。3.派生类的构造函数体中的操作。8.2派生类的构造函数与析构函数注意:在派生类构造函数中,只要基类不是使用无参的默认构造函数都要显式给出基类名和参数表。如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数。如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。派生类构造函数各部分执行次序:8.2派生类的构造函数与8.2派生类的构造函数与析构函数析构函数:析构函数的功能是作善后工作。

只要在函数体内把派生类新增一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增对象成员析构,最后对基类成员析构。8.2派生类的构造函数与析构函数析构函数:【例8.1】由在册人员类公有派生学生类【例8.1】由在册人员类公有派生学生类。我们希望基类和派生类共享相同的公有接口,只能采用公有派生来实现。基类:enum

Tsex{mid,man,woman};

classPerson{ stringIdPerson; //身份证号,18位数字

stringName; //姓名

TsexSex; //性别

intBirthday; //格式1986年8月18日写作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string); //构造函数

Person(); //默认的构造函数

~Person(); //析构函数【例8.1】由在册人员类公有派生学生类【例8.1】由在册人员【例8.1】由在册人员类公有派生学生类

voidSetName(string);

//修改名字

stringGetName(){returnName;}

//提取名字

voidSetSex(Tsexsex){Sex=sex;} //修改性别

TsexGetSex(){returnSex;} //提取性别

voidSetId(stringid){IdPerson=id;} //修改身份证号

stringGetId(){returnIdPerson;}

//提取身份证号

voidSetBirth(intbirthday){Birthday=birthday;}//修改生日

intGetBirth(){returnBirthday;}//提取生日

voidSetHomeAdd(string); //修改住址

stringGetHomeAdd(){returnHomeAddress;}

//提取住址

virtualvoidPrintInfo(); //输出个人信息

};//接口函数:【例8.1】由在册人员类公有派生学生类 voidSetN【例8.1】由在册人员类公有派生学生类派生的学生类:classStudent:publicPerson //定义派生的学生类{stringNoStudent; //学号

coursecs[30]; //30门课程与成绩public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);

//注意派生类构造函数声明方式

Student(); //默认派生类构造函数

~Student(); //派生类析构函数

SetCourse(string,int); //课程设置

intGetCourse(string); //查找成绩

voidPrintInfo(); //打印学生情况};structcourse{stringcoursename;

intgrade;};验证主函数【例8.1】由在册人员类公有派生学生类派生的学生类:clas8.2派生类的构造函数与析构函数注意:本例中标准C++字符串string是作为成员对象使用的(聚合),动态内存分配的构造和析构被封装起来,使用十分简单。如使用动态生成的C风格字符串,要考虑深复制,那要复杂得多。

提倡完善的类对象封装,不仅封装数据和对数据的操作,而且封装资源的动态分配与释放,形成一个完备的子系统。在一个有层次结构的类体系中资源的动态分配与释放应封装在成员对象中,如同使用标准的string字符串类那样。聚合是一种完善的封装。采用成员对象将大大简化层次结构的类体系中资源的动态分配与释放的处理方法,不再出现难度极大的多层次的深复制。8.2派生类的构造函数与析构函数注意:8.3多重继承与派生类成员标识(选读)由多个基类共同派生出新的派生类,这样的继承结构被称为多重继承或多继承(multiple-inheritance)

椅子床沙发(单继承)躺椅(多重继承)两用沙发(多重继承)图8.2椅子,床到两用沙发多重继承实例:8.3多重继承与派生类成员标识(选读)由多个基类共同派生在册人员学生(单继承)教职工(单继承)兼职教师(单继承)教师(单继承)行政人员(单继承)工人(单继承)研究生(单继承)行政人员兼教师(多重继承)在职研究生(多重继承)研究生助教(多重继承)图8.3大学在册人员继承关系8.3多重继承与派生类成员标识(选读)派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。

在册人员学生(单继承)教职工(单继承)兼职教师(单继承)教师8.3多重继承与派生类成员标识(选读)歧义性问题:参见图8.3,比如行政人员兼教师,在其基类教师中有一个“教职工编号”,另一基类行政人员中也有一个“教职工编号”,如果只讲教职工编号那么是哪一个基类中的呢?这两者可能是一回事,但计算机系统并不这么认为。进一步,如果“教职工编号”是由两个基类“教师”和“行政人员”共同的基类“教职工”类继承来的,只有同一个标识符,也不能用改标识符来区分。

唯一标识问题:通常采用作用域分辨符“::”:基类名::成员名;//数据成员基类名::成员名(参数表);//函数成员

8.3多重继承与派生类成员标识(选读)歧义性问题:唯一标classEGStudent

intNo在职学号………classGStudentintNo研究生号

……….classStudentintNo学生号

……….

classPersonintNo身份证号

……….classEmployeeintNo工作证号

……….classPersonintNo身份证号

……….图8.4(a)在职研究生派生类关系

定义EGStudent类对象EGStudent1,并假定派生全部为公有派生,而intNo全为公有成员:EGStud1.No//在职学号EGStud1.GStudent::No//研究生号EGStud1.GStudent.Student::No

//学生号

EGStud1.GStudent.Student.Person::No//身份证号EGStud1.Employee::No//工作证号EGStud1.Employee.Person::No

//身份证号两个身份证号从逻辑上讲应是一回事,但是物理上是分配了不同内存空间,是两个变量,请参见图8.4(b)。classEGStudentintNoclassGSPerson

Person

StudentEmployeeGStudent

EGStudentPerson成员

Person成员

Student新成员

GStudent新成员

Employee新成员

EGStudent新成员

图8.4(b)在职研究生派生类存储图

建议采用有确定字面意思的标识符,它可以被编译器简单区分出来。如果classPerson的身份证号标识为intIdPerson,则写为:EGStud1.GStudent::IdPersonEGStud1.Employee::IdPerson不必标出那么多层次的类,但写EGStud1.IdPerson是错的。

作用域分辨符不能嵌套使用,如:EGStud1.GStudent::Student::No //学生号EGStud1.GStudent::Student::Person::No //身份证号是错误的。8.3多重继承与派生类成员标识(选读)PersonPersonStudentEmployee8.3多重继承与派生类成员标识(选读)一般数据成员总是私有成员,派生类对基类的访问只能间接进行。访问身份证号,应通过classPerson中的公有成员函数(接口)GetNo()和SetNo()进行:EGStud1.Employee.Person::SetNo(intno);no=EGStud1.Employee.Person::GetNo();注意:8.3多重继承与派生类成员标识(选读)一般数据成员总是私【例8.2】由圆和高多重继承派生出圆锥。因为公有派生时,在派生类中不可以直接访问基类的私有成员,但可以直接访问基类的保护成员,当需要在派生类中访问基类的数据成员时,可以将它们定义为保护的,而不是私有的。

本例中类Circle为圆;类Line为高;类Cone为圆锥,由Circle和Line公有派生而来。在Cone类中,Circle和Line类的接口完全不变,可以直接调用,这就是公有派生的优点。在Cone的成员函数中可直接访问Circle和Line中的公有成员和保护成员。

【例8.2】由圆和高多重继承派生出圆锥检证主程序:圆类Circle定义高类Line定义圆锥类Cone定义【例8.2】由圆和高多重继承派生出圆锥。【例8.2】由圆和高虚基类的引入:在图8.4中,两个身份证号显然是不合理的。可以把classPerson这个共同基类设置为虚基类,这样就仅有一个Person基类成员,从不同路径继承来的同名数据成员(身份证号)在内存中就是同一个数据。8.4虚基类(选读)注意:virtual关键字只对紧随其后的基类名起作用:classStudent:virtual

publicPerson{...};classEmployee:virtual

publicPerson{...};虚基类(virtualbaseclass)定义:class

派生类名:virtual访问限定符基类类名{...};class

派生类名:访问限定符

virtual基类类名{...};虚基类的引入:8.4虚基类(选读)注意:虚基类(virt8.4虚基类(选读)图8.5采用虚基类后在职研究生类储存图StudentGStudentEGStudentPersonStudent新成员GStudent新成员PersonEmployee新成员Person成员EGStudent新成员PersonPersonEmployee这种继承称为虚拟继承虚拟继承:在Person的位置上放的是指针,两个指针都指向Person成员存储的内存。这种继承称为虚拟继承(virtualinheritance)。8.4虚基类(选读)图8.5采用虚基类后在职研究生类8.4虚基类(选读)派生类名::派生类名(参数总表):基类名1(参数名表1)《,基类名2(参数名表2),……,基类名n(参数名表n)》,《成员对象名1(成员对象参数名表1),……,成员对象名m(成员对象参数名表m)》,底层虚基类名1(参数名表1)《,……,底层虚基类名r(参数名表r)》{……//派生类新增成员的初始化};//所列出的成员对象名全部为新增成员对象的名字在多层虚拟继承构造函数中,基类名不仅要列出直接基类,而且要列出底层虚基类,否则编译器认为出错。虚拟继承的构造函数:8.4虚基类(选读)派生类名::派生类名(参数总表):基8.4虚基类(选读)在派生类对象的创建中:首先是虚基类的构造函数并按它们声明的顺序构造。第二批是非虚基类的构造函数按它们声明的顺序调用。第三批是成员对象的构造函数。最后是派生类自己的构造函数被调用。构造函数执行次序:8.4虚基类(选读)在派生类对象的创建中:构造函数执行次8.4虚基类(选读)【例8.3】在采用虚基类的多重继承中,构造与析构的次序。classDclass:publicBclass1,virtualBclass3,virtualBclass2{Objectobject;public:Dclass():object(),Bclass2(),Bclass3(),Bclass1(){cout<<"派生类建立!\n";}~Dclass(){cout<<"派生类析构!\n";}};voidmain(){ Dclassdd;cout<<"主程序运行!\n";}8.4虚基类(选读)【例8.3】在采用虚基类的多重继承中运行结果:ConstructorBclass3 //第一个虚拟基类,与派生类析构函数排列无关ConstructorBclass2 //第二个虚拟基类ConstructorBclass1 //非虚拟基类ConstructorObject //对象成员派生类建立!主程序运行!派生类析构!deconstructorObject //析构次序相反deconstructorBclass1deconstructorBclass2deconstructorBclass3//析构的次序与构造的次序相反。8.4虚基类(选读)运行结果:8.4虚基类(选读)对照图8.5,尽管Employee和Student的构造函数都包含Person的构造函数,但并未真正调用。唯一的一次调用是在EGStudent构造函数中。如是非虚基类,则有两次调用。8.4虚基类(选读)【例8.4】虚基类在多层多重继承中的应用

——在职研究生类定义。以虚基类定义公有派生的学生类以虚基类定义公有派生的研究生类以虚基类定义公有派生的教职工类多重继承的以虚基类定义公有派生的在职研究生类对照图8.5,尽管Employee和Student的构造函数一、派生类与基类:

在任何需要基类对象的地方都可以用公有派生类的对象来代替,这条规则称赋值兼容规则。它包括以下情况:8.5派生类应用讨论1.

派生类的对象可以赋值给基类的对象,这时是把派生类对象中从对应基类中继承来的成员赋值给基类对象。反过来不行,因为派生类的新成员无值可赋。2.

可以将一个派生类的对象的地址赋给其基类的指针变量,但只能通过这个指针访问派生类中由基类继承来的成员,不能访问派生类中的新成员。同样也不能反过来做。3.

派生类对象可以初始化基类的引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的成员。【例8.5】为例8.1定义复制函数,实现深复制。一、派生类与基类:8.5派生类应用讨论1.派生类的二、继承与聚合

继承使派生类可以利用基类的成员,如果我们把基类的对象作为一个新类的对象成员,也可以取得类似的效果。派生类采用继承方法,成员对象是聚合的概念。基类在派生类中只能继承一个(间接基类不在讨论之中)不能同时安排两个,否则成员名即使使用域分辨符也会发生冲突:classA{public:intK;...};classB:publicA,publicA{...};两个A无论如何无法分辨出来。如果要用两个A只能采用成员对象。

更深入地探讨后会发现:成员对象体现了封装更深层次的含义。在派生类和它的基类中是不应该有内存的动态分配的,动态分配的部分应该封装在成员对象中,在该成员对象的析构函数中释放内存,在该成员对象中提供深复制。类string就是如此。它的内部就是一个完备的小系统。这样程序员就可以放心地使用它,而不需要为它做任何事情。8.5派生类应用讨论二、继承与聚合8.5派生类应用讨论8.6多态性与虚函数问题:如何用同一个函数(同名函数)求三角形、长方形、梯形等不同基本形状的面积?8.6多态性与虚函数问题:8.6多态性与虚函数多态性:多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能。在C++中有两种多态性

编译时的多态性

运行时的多态性

运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定。它是通过类继承关系和虚函数来实现的。目的也是建立一种通用的程序。通用性是程序追求的主要目标之一。

通过函数的重载和运算符的重载来实现的。8.6多态性与虚函数多态性:在C++中有两种多态性编译8.6多态性与虚函数8.6.1虚函数的定义

8.6.4动态绑定

(选读)

8.6.2纯虚函数

8.6.3

继承与多态的应用——单链表派生类(选读)

8.6多态性与虚函数8.6.1虚函数的定义8.6.1虚函数的定义虚函数的概念:虚函数是一个类的成员函数,定义格式如下:virtual返回类型函数名(参数表);关键字virtual指明该成员函数为虚函数。virtual仅用于类定义中,如虚函数在类外定义,不可再加virtual。当一个类的某个成员函数被定义为虚函数,则由该类派生出来的所有派生类中,该函数始终保持虚函数的特征。8.6.1虚函数的定义虚函数的概念:当一个类的某个成员函8.6.1虚函数的定义当在派生类中重新定义虚函数(overridingavirtualfunction,亦译作超载或覆盖)时,不必加关键字virtual。但重新定义时不仅要同名,而且它的参数表和返回类型全部与基类中的虚函数一样,否则出错。虚函数与在8.1.1节中介绍的派生类的第二步——改造类成员,同名覆盖(override)有关:如未加关键字virtual,则是普通的派生类中的新成员函数覆盖基类同名成员函数(当然参数表必须一样,否则是重载),可称为同名覆盖函数,它不能实现运行时的多态性。

虚函数定义要点:8.6.1虚函数的定义当在派生类中重新定义虚函数(ove虚函数与运行时的多态性:【例8.6】计算学分。可由本科生类派生出研究生类,但它们各自的从课程学时数折算为学分数的算法是不同的,本科生是16个学时一学分,而研究生是20个学时一学分。8.6.1虚函数的定义【例8.7】计算学分。派生类定义不再重复。虚函数与运行时的多态性:8.6.1虚函数的定义【例8.7成员函数设置为虚函数的要点:1.派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外。2.只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。3.静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。4.实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性。8.6.1虚函数的定义成员函数设置为虚函数的要点:1.派生类中定义虚函数必须与5.内联函数因为每个对象有独立的一份函数代码,无映射关系,不能作为虚函数。6.析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。7.函数执行速度要稍慢一些。为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。8.如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。正确的定义必须不包括virtual。8.6.1虚函数的定义5.内联函数因为每个对象有独立的一份函数代码,无映射关系,不8.6.1虚函数的定义【例8.5_1】根据赋值兼容规则可以用基类的指针指向派生类对象,如果由该指针撤销派生类对象,则必须将析构函数说明为虚函数,实现多态性,自动调用派生类析构函数。通常要求将类设计成通用的,无论其他程序员怎样调用都必须保证不出错,所以必须把析构函数定义为虚函数。下面把【例8.5】析构函数改造为虚函数classPerson{

//数据成员略public:

virtual~Person();

//只需在此声明一次,派生类的析构函数全为虚函数

};

//其他成员函数略Person::~Person(){cout<<"Person析构函数"<<endl;}8.6.1虚函数的定义【例8.5_1】根据赋值兼容规则可Person*per4;Student*stu4=newStudent;//动态建立对象*stu4*stu4=stu1;//把stu1的数据拷入*stu4stu4->PrintStudentInfo();per4=stu4;deleteper4;//用基类指针撤销派生类,动态生成的对象必须显式撤销8.6.1虚函数的定义在主函数中添加以下内容:

通过在析构函数中加显示语句发现先调Student析构函数,后调Person析构函数。这里再次强调动态生成的对象必须显式撤销。Person*per4;8.6.1虚函数的定义在主函数纯虚函数:纯虚函数(purevirtualfunction)是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实现依赖于不同的派生类。8.6.2纯虚函数纯虚函数的定义:virtual返回类型函数名(参数表)=0;含有纯虚函数的基类是不能用来定义对象的。纯虚函数没有实现部分,不能产生对象,所以含有纯虚函数的类是抽象类。纯虚函数:8.6.2纯虚函数纯虚函数的定义:含有纯虚函数1定义纯虚函数时,不能定义虚函数的实现部分。即使是函数体为空也不可以,函数体为空就可以执行,只是什么也不做就返回。而纯虚函数不能调用。2“=0”表明程序员将不定义该函数,函数声明是为派生类保留一个位置。“=0”本质上是将指向函数体的指针定为NULL。3在派生类中必须有重新定义的纯虚函数的函数体,这样的派生类才能用来定义对象。8.6.2纯虚函数定义纯虚函数的要点:1定义纯虚函数时,不能定义虚函数的实现部分。即使是函数体【例8.8】学校对在册人员进行奖励,依据是业绩分,但是业绩分的计算方法只能对具体人员进行,如学生,教师,行政人员,工人,算法都不同,所以可以将在册人员类作为一个抽象类,业绩计算方法作为一个纯虚函数。在主函数中全部用指向基类的指针来调用8.6.2纯虚函数业绩分基类定义业绩分学生派生类定义业绩分教师派生类定义验证主函数【例8.8】学校对在册人员进行奖励,依据是业绩分,但是业绩分【例8.9】用虚函数来实现辛普生法求函数的定积分。8.6.2纯虚函数纯虚函数实现通用算法:辛普生法求定积分类在派生类中加被积函数:验证主函数【例8.9】用虚函数来实现辛普生法求函数的定积分。8.6.28.6.3继承与多态的应用——单链表派生类(选读)【例8.10】通用单链表派生类。第一步改造【例7.4】的头文件,不采用模板类,而采用虚函数实现多态性,达到通用的目的。结点类数据域被改造为指针,而把数据放在一个抽象类中,由指针与之建立联系。数据域(指向抽象数据类的指针)由抽象类派生的数据类对象(如串对象)指针域(指向下一结点)结点类对象动态建立的数据类对象图8.9结点构造8.6.3继承与多态的应用——单链表派生类(选读)【例8classObject{//数据类为抽象类public:Object(){}virtualbooloperator>(Object&)=0;

//纯虚函数,参数必须为引用或指针

virtualbooloperator!=(Object&)=0;

//纯虚函数,参数必须为引用或指针

virtualvoidPrint()=0;//纯虚函数

virtual~Object(){}};//析构函数可为虚函数,构造函数不行8.6.3继承与多态的应用——单链表派生类(选读)结点组织,采用结点类加数据类数据类定义:本题要点:采用虚函数实现多态性,达到通用的目的。堆内存的分配与释放,关键不是创建,而是释放!classObject{//数据类为抽象类说明:数据抽象类中含有三个纯虚函数:输出函数和两个比较函数。当抽象类在派生时重新定义三个纯虚函数,可以进行各种类型,包括类和结构对象的比较和输出。本例介绍程序总体组成为主,链表的操作由学生自己仔细阅读。8.6.3继承与多态的应用——单链表派生类(选读)抽象类中的析构函数也是虚函数,这一点非常重要,当抽象类派生的数据类的数据部分是动态产生的,而由结点类删除释放数据类对象时,必须由数据类的析构函数来释放该类对象数据部分占用的动态分配的内存。这时必须重新定义析构函数。说明:本例介绍程序总体组成为主,链表的操作由学生自己仔细阅读ClassNode{Object*info;//数据域用指针指向数据类对象

Node*link;//指针域public:Node();//生成头结点的构造函数

~Node();//析构函数

voidInsertAfter(Node*P);//在当前结点后插入一个结点

Node*RemoveAfter();

//删除当前结点的后继结点,返回该结点备用

voidLinkinfo(Object*obj);//把数据对象连接到结点

friendclassList;

//以List为友元类,List可直接访问Node的私有函数,};8.6.3继承与多态的应用——单链表派生类(选读)结点类定义:ClassNode{8.6.3继承与多态的应用——单链classList{Node*head,*tail;//链表头指针和尾指针public:List();//构造函数,生成头结点(空链表)~List();//析构函数

voidMakeEmpty();//清空链表,只余表头结点

Node*Find(Object&obj);

//搜索数据域与定值相同的结点,返回该结点的地址

intLength();//计算单链表长度

voidPrintList();//打印链表的数据域

voidInsertFront(Node*p);//可用来向前生成链表

voidInsertRear(Node*p);//可用来向后生成链表

voidInsertOrder(Node*p);//按升序生成链表

Node*CreatNode();//创建一个结点(孤立结点)Node*DeleteNode(Node*p);};//删除指定结点8.6.3继承与多态的应用——单链表派生类(选读)链表类定义:classList{8.6.3继承与多态的应用——单链第二步,取代模板定义泛型类型为具体类型(包括类)的步骤是由抽象类派生数据类。数据类的数据采用字符类串string,动态分配和释放内存都在string类中完成。为了完成数据类的比较和输出,超载了比较运算符和输出函数(虚函数)。数据类的比较实际是字符串string的比较。8.6.3继承与多态的应用——单链表派生类(选读)classStringObject:publicObject{stringsptr;public:StringObject(){sptr="";}StringObject(strings){sptr=s;}~StringObject();//析构函数

booloperator>(Object&);//大于函数

booloperator!=(Object&);//不等于函数

voidPrint();//打印函数};验证主函数运行结果第二步,取代模板定义泛型类型为具体类型(包括类)的步骤是由抽分析与比较:

在该程序中,特别要仔细揣摩堆内存的分配与释放。删除一个结点时系统自动调用结点类析构函数释放结点占用的动态内存,而结点类析构函数自动调用数据域类虚析构函数,数据域类析构函数自动调用string类的析构函数释放所占用的动态内存。一环套一环,一步都不能错。这是使用动态内存分配的关键。即关键不是创建,而是释放!

运行时的多态性需要维护一个动态指针表才能正确指向各相关类中的同名虚函数。所以多态与模板比较,模板的效率更高,标准模板库中用容器来泛型化数据结构中的许多算法。对数据结构的使用当然借助模板库。多态不适用于性能要求很高的实时应用程序,但继承与多态可用与其它更多方面,每一种技术都有可以充分发挥自己能力的地方。8.6.3继承与多态的应用——单链表派生类(选读)分析与比较:8.6.3继承与多态的应用——单链表派生类(动态绑定(dynamicbinding)亦称滞后绑定(latebinding),对应于静态绑定(staticbinding)。如果使用对象名和点成员选择运算符“.”引用特定的一个对象来调用虚函数,则被调用的虚函数是在编译时确定的(称为静态绑定)

如果使用基类指针或引用指明派生类对象并使用该指针调用虚函数(成员选择符用箭头号“->”),则程序动态地(运行时)选择该派生类的虚函数,称为动态绑定。8.6.4动态绑定(选读)绑定是指计算机程序自身彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把一条消息和一个对象的操作相结合的过程。动态绑定(dynamicbinding)亦称滞后绑定(la图8.9虚函数调用的控制流程“dog”StringObject动态无名对象StringObject动态无名对象“cat”指向Object类指针指向结点类指针指向Object类指针指向结点类指针指向Object类指针Λ指向结点类指针StringObject动态无名对象“cock”·

·

·析构函数指针0比较函数指针0输出函数指针StringObject虚函数表抽象类Object虚函数表析构函数指针比较函数指针输出函数指针ComplexObject虚函数

析构函数指针

比较函数指针

输出函数指针·

·

·默认析构函数释放动态串析构函数串比较函数打印串函数默认析构函数复数模大小比较函数打印复数函数图8.9虚函数调用的控制流程“dog”StringObj8.6.4动态绑定(选读)

C++编译器编译含有一个或几个虚函数的类及其派生类时,对该类建立虚函数表(Virtualfunctiontable,vtable)。虚函数表使执行程序正确选择每次执行时应使用的虚函数。多态是由复杂的数据结构实现的,参见图8.10。图8.10是以【例8.10】为基础的,不过增加了一个由抽象类Object派生的复数数据类ComplexObject。图中列出了基类和各派生类的虚函数表,这些表是由指向函数的指针组成的。

8.6.4动态绑定(选读)C++编译器编译含有一个8.6.4动态绑定(选读)

还有第二层指针,在实例化带虚函数的类(创建对象)时,编译器在对象前加上一个指向该类的虚函数表的指针。 第三层指针是链表结点类对象中指向抽象基类Object的指针(这也可以是引用,但本例是指针)。虚函数的调用是这样进行的,考虑虚函数Compare(),则看含“cat”的结点。由该结点的info指针找到含“cat”的无名对象,再由对象前的指针找到StringObject虚函数表,移动4个字节(一个指针占4个字节)找到比较函数指针,进入串比较函数。8.6.4动态绑定(选读)还有第二层指针,在实例完第八章继承与派生谢谢!完第八章继承与派生谢谢!【例8.1】由在册人员类公有派生学生类Person::Person(stringid,stringname,Tsexsex, intbirthday,stringhomeadd){ IdPerson=id; Name=name; Sex=sex; Birthday=birthday; HomeAddress=homeadd;}//作为一个管理程序,这个构造函数并无必要,因为数据总是另外输入的。仅为说明语法存在。分析构造函数:【例8.1】由在册人员类公有派生学生类Person::Per【例8.1】由在册人员类公有派生学生类Person::Person(){ IdPerson="#";Name="#";Sex=mid; Birthday=0;HomeAddress="#";}分析默认的构造函数:分析析构函数:Person::~Person(){}//string内部动态数组的释放,由string自带的析构函数完成【例8.1】由在册人员类公有派生学生类Person::Per【例8.1】由在册人员类公有派生学生类voidPerson::SetName(stringname){ Name=name;//拷入新姓名}修改名字:voidPerson::SetHomeAdd(stringhomeadd){ HomeAddress=homeadd;}修改住址:【例8.1】由在册人员类公有派生学生类voidPerson【例8.1】由在册人员类公有派生学生类voidPerson::PrintInfo(){

inti; cout<<"身份证号:"<<IdPerson<<'\n'<<"姓名:"<<Name<<'\n'<<"性别:";

if(Sex==man)cout<<"男"<<'\n';

else

if(Sex==woman)cout<<"女"<<'\n';

elsecout<<""<<'\n'; cout<<"出生年月日:"; i=Birthday; cout<<i/10000<<"年"; i=i%10000; cout<<i/100<<"月"<<i%100<<"日"<<'\n‘<<"家庭住址:"<<HomeAddress<<'\n';}输出个人信息:【例8.1】由在册人员类公有派生学生类voidPerson【例8.1】由在册人员类公有派生学生类Student::Student(stringid,stringname,Tsexsex,intbirthday,stringhomeadd,stringnostud):Person(id,name,sex,birthday,homeadd){ //注意Person参数表不用类型

NoStudent=nostud;

for(inti=0;i<30;i++) //课程与成绩清空

{ cs[i].coursename="#"; cs[i].grade=0; }}派生类构造函数:【例8.1】由在册人员类公有派生学生类Student::St【例8.1】由在册人员类公有派生学生类Student::Student()//基类默认的无参数构造函数不必显式给出{ inti;

NoStudent="";

for(i=0;i<30;i++)//课程与成绩清零,将来由键盘输入

{ cs[i].coursename=""; cs[i].grade=0; }}Student::~Student(){}

//基类析构函数以及成员对象析构函数自动调用默认派生类构造函数:派生类析构函数:【例8.1】由在册人员类公有派生学生类Student::StintStudent::SetCourse(stringcoursename,intgrade){

boolb=false;//标识新输入的课程,还是更新成绩

inti;

for(i=0;i<30;i++){

if(cs[i].coursename=="#") { //判表是否进入未使用部分

cs[i].coursename=coursename; cs[i].grade=grade; b=false;break;}

else

if(cs[i].coursename==coursename){ //是否已有该课程记录

cs[i].grade=grade;b=true;break;}}

if(i==30)return0;//成绩表满返回0

if(b)return1;//修改成绩返回1

elsereturn2;//登记成绩返回2}学生类课程设置函数:intStudent::SetCourse(string【例8.1】由在册人员类公有派生学生类intStudent::GetCourse(stringcoursename){inti;for(i=0;i<30;i++)

if(cs[i].coursename==coursename)

returncs[i].grade;return-1;}//找到返回成绩,未找到返回-1查找学生课程成绩函数:【例8.1】由在册人员类公有派生学生类intStudent【例8.1】由在册人员类公有派生学生类voidStudent::PrintInfo(){ Person::PrintInfo();

inti; cout<<"学号:"<<NoStudent<<'\n';

for(i=0;i<30;i++)//打印各科成绩

if(cs[i].coursename!="#") cout<<cs[i].coursename <<'\t'<<cs[i].grade<<'\n';

else

break; cout<<"--------完--------"<<endl;}打印学生情况函数:【例8.1】由在册人员类公有派生学生类voidStuden例8.1验证用主函数:intmain(void){

chartemp[30];

inti,k;Personper1("320102820818161","沈俊",man,19820818,"南京四牌楼2号");Personper2;per2.SetName("朱明");per2.SetSex(woman);per2.SetBirth(19780528);per2.SetId("320102780528162");per2.SetHomeAdd("南京市成贤街9号");per1.PrintInfo();per2.PrintInfo();Studentstu1("320102811226161","朱海鹏",man,19811226,"南京市黄浦路1号","06000123");例8.1验证用主函数:intmain(void)cout<<"请输入各科成绩:"<<'\n';

//完整的程序应输入学号,查找,再操作

while(1)//输入各科成绩,输入"end"停止

{cin>>temp;//输入格式:物理80

if(!strcmp(temp,"end"))break;cin>>k;i=stu1.SetCourse(temp,k);

if(i==0)cout<<"成绩列表已满!"<<'\n';

else

if(i==1)cout<<"修改成绩"<<'\n';

elsecout<<"登记成绩"<<'\n';}stu1.PrintInfo();while(1){ cout<<"查询成绩"<<'\n'<<"请输入科目:"<<'\n'; cin>>temp;

if(!strcmp(temp,"end"))break; k=stu1.GetCourse(temp);

if(k==-1)cout<<"未查到"<<'\n';

elsecout<<k<<'\n';

温馨提示

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

评论

0/150

提交评论