




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
PARTIII
面
向
对
象
的
程
序
设
计引
言概
要
回
顾基于过程的程序设计基于对象的程序设计面向对象的程序设计程序的主体:函数类的生成、设计类对象的使用C++过程化语言基础程序的主体:类问
题
的
回顾俄罗斯方块的设计与实现基于过程的设计与实现基于对象的设计与实现方块对象:七种方块对象图框对象:负责绘制俄罗斯方块运行的图框界面判定对象:负责消行、统计分数、方块到顶游戏终结基
于
对
象
的
解
决
方
案基
于
对
象
的
解
决
方
案
分
析七种方块:公有属性:颜色、速度、……不同属性:形状、……共同操作:右移、左移、下移、变形各种操作由于形状的不同而有不同的实现这7种方块的移动和变形的实现方法不同,显示也不同,因此不能用同一个类来描述设计7个类分别描述7种方块7个方块类中又有很多相同的,如颜色等数据属性,都要四种移动操作,只是各自的实现方法有所不同问题1:共同的数据属性在7个类中各自描述一次,显得繁琐,重复,能够简化为统一描述?问题2:相同的操作能否在统一描述的同时方便地根据方块类型调用不同的实现?
——继承和多态结
论仅仅支持基于类的设计、生成和类对象的使用是不能称为面向对象的程序设计面向对象程序设计的四大主要特点抽象封装继承多态基于对象的程序设计面向对象的程序设计PARTIII纲
要010203继承与派生(重点)多态性与虚函数(重点)输入输出流(常用)04C++工具继
承
与
派
生问
题
回
顾
与
分
析共同的数据属性在7个方块类中各自描述一次,显得繁琐,重复,能够简化为统一描述?分析先统一描述所有方块的共性以及对全体方块的处理功能;描述某一种方块时,首先说明它是其中之一,然后再逐一描述这种方块的个性——面向对象的程序设计中类的继承与派生继
承
与
派
生类
的
继
承派生类可以获得基类的已有特性派生类继承了基类的所有数据成员和成员函数类的继承是层次结构的基类是派生类的抽象类的派生基类产生了一个和具有基类各种特性的新的子类派生类可以对成员作必要的增加和调整派生类又可以作为基类再派生出新的派生类派生类是基类的具体化继承派生在一个已经存在的类(基类/父类)的基础之上建立一个新的类(派生类/子类)继
承
与
派
生
的
基
本
形
式单
继
承
和
多
重
继
承单继承一个派生类只能由一个基类派生而来一个子类只有一个父类多重继承一个派生类由多个(>=2)基类派生而成一个子类有两个或者两个以上的父类派
生
类
的
声
明一般形式class<派生类名>:<继承方式><基类名>{<派生类新定义成员>};派
生
类
实
例基类(普通学生类)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生类声明(大学生类)classundergstudent
:publicstudent{private:char*major;//新增加的数据成员floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成员函数voidprint(){……}}基类(普通学生类)派生类声明(大学生类)大学生类(未考虑访问属性)
intID;char*name;
floatscore[40];
char*major;
print_major(){……}
voidprint(){…..}…….派
生
类
的
构
成两大部分从基类继承来的成员声明派生类时增加的部分每一部分都分别包括数据成员和成员函数构成方式并非基类的成员和派生类增加成员的简单加和接收基类的全部成员调整从基类接收的成员声明派生类时增加的成员根据需要增加成员定义派生类的构造函数和析构函数(不能从基类继承而来)基
类
成
员
的
接
收
和
调
整无条件地全部接收不接收析构函数和构造函数接收到的基类成员可以调整通过指定继承方式改变成员的访问属性可以声明同名成员加以覆盖派
生
类
的
继
承
方
式三种继承方式publicprivateprotected不同的继承方式决定了基类成员在派生类中访问属性派
生
类
成
员
的
访
问
属
性分情况处理基类的成员函数访问基类成员
✔派生类成员函数访问派生类自己增加的成员
✔基类的成员函数访问派生类的成员
✗派生类外访问派生类的成员根据成员的访问属性判定能否由类外访问该成员派生类的成员函数访问基类的成员派生类外访问基类的成员核心问题:如何确定基类成员在派生类中的访问属性基类成员声明的访问属性派生类对基类的继承方式三
种
继
承
方
式
下
派
生
类
中
基
类
成
员
的
访
问
控
制
权
限
继承
方式基类成员公有继承私有继承保护继承公有成员公有私有保护私有成员派生类成员不可访问派生类成员不可访问派生类成员不可访问保护成员保护私有保护公
有
继
承基类的私有成员并没有成为派生类的私有成员基类的私有成员仅仅只有基类的成员函数才能应用基类的私有成员是派生类的不可访问的成员基类的私有成员只能通过基类的公有成员函数加以访问实例:基类student派生类undergstudent(公有继承)假设一创建一个对象freshman是一个大一新生(Bob,10009,CS,……)如何能够正确获取name、ID、major等信息?方
案
一主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:cout<<name;cout<<ID;cout<<major;原因:name和ID是基类的私有成员,在派生类中不可访问方
案
二基类中公有成员函数get_namecout<<name;基类中公有成员函数get_IDcout<<ID;派生类中公有成员函数get_majorcout<<major;主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取公有继承下,基类的公有成员在派生类中仍为公有,可在派生类外访问方
案
三主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:get_name;//调用基类的成员函数get_ID;//调用基类的成员函数cout<<major;基类中公有成员函数get_name定义如下:cout<<name;基类中公有成员函数get_ID定义如下:cout<<ID;公
有
继
承
的
特
点
和
延
伸特点:较好地保留了基类的特征几乎具有基类的全部功能公有继承方式下的公有派生类是基类的子类型基类和公有派生类之间的类型转换回顾:不同数据类型之间在一定条件下可以进行类型的转换——赋值兼容example:整型数据可以复制给双精度型的变量基类对象和子类(公有派生类)之间也有赋值兼容关系基
类
和
子
类
间
的
类
型
转
换派生类对象向基类对象赋值赋值时舍弃派生类自己的成员赋值后基类对象的成员并未扩充//set_major是undergstudent的公有成员函数undergstudentugS1(Bob,10009,CS,….);studentS1;S1=ugS1;//✔S1.set_major(“EE”);//✗ugS1.set_major(“EE”);//✔不能用基类对象对子类对象赋值同一基类的不同子类对象之间不能赋值派生类对象可以向基类对象的引用进行赋值或者初始化回顾:变量x的引用是变量x的别名,和变量x共享同一存储空间studentS1;undergstudentugS1(Bob,10009,CS,….);student&S1_ref=S1;S1_ref=ugS1;//✔,用子类对象ugS1对S1_ref进行赋值student&S2_ref=ugS1;/*✔,定义了一个student对象的应用S2_ref,并用ugS1进行初始化*/S1_ref.set_major(“EE”);//✗S1_ref和S2_ref仍然只是基类对象的引用,并不是ugS1(子类对象)的别名,S1_ref、S2_ref和ugS1只共享了ugS1中属于基类部分的存储空间如果函数的参数是基类对象或者基类对象的引用,相应的实参可以用子类对象参数传递时完成自动类型转换派生类对象的地址可以赋给指向基类对象的指针变量指向基类对象的指针变量也可以指向派生类对象指向基类对象的指针变量只能访问派生类中的基类成员,而不能访问派生类增加的成员studentS1;undergstudentugS1(Bob,10009,CS,….);student*S_ptr=&S1;S_ptr=&ugS1;/*✔,子类对象ugS1的地址可以赋值给指向基类对象的指针变量*/S_ptr->set_major(“EE”);//*✗不能访问派生类增加的成员私有继承私有基类的公有成员和保护成员相当于派生类中的私有成员派生类的成员可以访问私有基类的公有成员和保护成员,但是派生类外不能访问实例:情况与前述相同,唯一的区别是派生类undergstudent以私有继承的方式由student类派生而成方
案
一主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:cout<<name;cout<<ID;cout<<major;原因:name和ID是基类的私有成员,公有继承下在派生类中已不可访问,私有继承下依然不可行方
案
二基类中公有成员函数get_namecout<<name;基类中公有成员函数get_IDcout<<ID;派生类中公有成员函数get_majorcout<<major;主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取原因:私有继承下,基类的公有成员等同于派生类的私有成员,无法在派生类外访问方
案
三主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:get_name;//调用基类的成员函数get_ID;//调用基类的成员函数cout<<major;基类中公有成员函数get_name定义如下:cout<<name;基类中公有成员函数get_ID定义如下:cout<<ID;保
护
继
承保护成员受保护的成员不能为类外访问,但是可以被派生类的成员函数所引用基类(普通学生类)classstudent{private:intID;char*name;floatscore[11];public:……};派生类声明(大学生类)classundergstudent
:publicstudent{private:char*major;//新增加的数据成员floatscore[40];public:……}classstudent{protected:intID;char*name;floatscore[11];public:……};保
护继承保护基类的公有成员和保护成员相当于派生类中的保护成员派生类的成员可以访问保护基类的公有成员和保护成员,但是派生类外不能访问实例:情况与前述相同,但是派生类undergstudent以保护继承的方式由student类派生而成第
一
种
情
形(ID和name为私有数据)基类(普通学生类)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生类声明(大学生类)classundergstudent
:protectedstudent{private:char*major;//新增加的数据成员floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成员函数voidprint(){……}}方
案
一主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:cout<<name;cout<<ID;cout<<major;原因:name和ID是基类的私有成员,保护继承下在派生类中依然不可访问方
案
二基类中公有成员函数get_namecout<<name;基类中公有成员函数get_IDcout<<ID;派生类中公有成员函数get_majorcout<<major;主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取原因:保护继承下,基类的公有成员等同于派生类的保护成员,无法在派生类外访问方
案
三主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:get_name;//调用基类的成员函数get_ID;//调用基类的成员函数cout<<major;基类中公有成员函数get_name定义如下:cout<<name;基类中公有成员函数get_ID定义如下:cout<<ID;第二种情形
(ID和name为保护成员数据)基类(普通学生类)classstudent{protected:
intID;char*name;private:floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生类声明(大学生类)classundergstudent
:protectedstudent{private:char*major;//新增加的数据成员floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成员函数voidprint(){……}}方
案
一主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:cout<<name;cout<<ID;cout<<major;原因:name和ID是基类的保护成员,公有继承下和保护继承下派生类中可以访问方
案
二基类中公有成员函数get_namecout<<name;基类中公有成员函数get_IDcout<<ID;派生类中公有成员函数get_majorcout<<major;主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取原因:保护继承下,基类的公有成员等同于派生类的保护成员,无法在派生类外访问方
案
三主函数调用freshman的公有成员函数get_data派生类中get_data如下定义:get_name;//调用基类的成员函数get_ID;//调用基类的成员函数cout<<major;基类中公有成员函数get_name定义如下:cout<<name;基类中公有成员函数get_ID定义如下:cout<<ID;保
护
成
员
和
保
护
继
承
的
效
用在类的层次继承结构中找到数据共享与成员隐蔽之间的最佳均衡如果需要在派生类中引用基类的某些成员,应当将基类中的这些成员声明为protected,而非private以student类中的ID和name为例如果声明成为private,将造成派生类无法访问的局面,使得派生类的使用很不方便如果声明成为public,使得基类的数据隐蔽性受到一定的损伤如果声明成为protected,派生类可以访问从而使得基类和派生类之间可以实现数据共享,基类和派生类的外部都不能访问,保障了数据的隐蔽性多
级
派
生
下
的
访
问
属
性类的层次继承结构导致了类的多级派生基类基类的派生类派生类的派生类……直接派生类vs.间接派生类直接基类vs.间接基类多级派生情况下各成员的访问属性在基类和其直接派生类之间按照一级派生访问属性的原则确定各成员的访问属性后逐级向下迁移派
生
类
的
构
造
函
数回顾:构造函数的作用对类中的数据成员进行初始化派生类的构造函数的特殊性派生类的数据成员构成较为特殊从基类中接收来的数据成员派生类自己增加的数据成员派生类并没有继承基类的构造函数派生类的构造函数的初始化工作基类数据成员的初始化工作
派生类增加的数据成员的初始化工作派
生
类
实
例基类(普通学生类)classstudent{private:intID;char*name;floatscore[11];public:student(intn1,char*nm1){……}~student(){deletename;}voidprint(){…..}voidget_name(){……}……};派生类声明(大学生类)classundergstudent
:publicstudent{private:char*major;//新增加的数据成员floatscore[40];public:……voidget_data{……}print_major(){……}//新增加的成员函数voidprint(){……}}实
例:大学生类undergstudent的构造函数方案一undergstudent(intn,stringstu_name,stringstu_major,float*scoreList){ID=n;name=stu_name;major=stu_major;score=scoreList;}派生类无法访问基类的私有成员数据方案二undergstudent(intn,stringstu_name,stringstu_major,float*scoreList){student(n,stu_name);//调用基类构造函数major=stu_major;score=scoreList;}不能在派生类构造函数体中显式调用构造函数!虽然能通过编译,但结果不对派
生
类
构
造
函
数
的
解
决
之
道方案三undergstudent(intn,stringstu_name,stringstu_major,float*scoreList):student(n,stu_name);//调用基类构造函数{/*派生类的函数体中只对派生类新增的数据成员进行初始化*/major=stu_major;score=scoreList;}解决的办法是通过成员初始化表来完成基类数据成员的初始化,在成员初始化表中可以显式调用基类构造函数派生类的函数体完成新增数据成员的初始化派
生
类
构
造
函
数
的
基
本
形
式<派生类名>(<总参数表>):<基类构造函数名>(<参数表1>){<派生类数据成员的初始化>};说明:1、如果派生类构造函数在类的外面定义,类体中只需要写这个函数的声明:<派生类名>(<总参数表>);2、总参数表中的参数包括了基类构造函数所需的参数和派生类新增数据成员初始化所需的参数
生成一个类对象(10009,bob,CS,……)
undergstudent(n,stu_name,stu_major,scorelist):student(n,stu_name)构造函数的成员初始化表有
子
对
象
的
派
生
类
的
构
造
函
数子对象类的数据成员本身就是一个类对象classundergstudent
:publicstudent{private:char*major;floatscore[40];
Teacher*tutor;//子对象public:……}classteacher
{private:intID;char*name;
char*title;public:……}分析:对象建立时需要对它的数据成员进行初始化派生类构造函数对其数据成员进行初始化的时候也需要对其中的子对象进行初始化在成员初始化表中显式调用子对象的构造函数undergstudent(intn,stringstu_name,stringstu_major,float*scoreList,intT_n,stringT_name,stringT_title):student(n,stu_name),tutor(T_n,T_name,T_title)派
生
类
构
造
函
数
的
一
般
形
式<派生类名>(<总参数表>):<基类名>(<参数表1>),<对象成员名>(<参数表2>){<派生类其它数据成员的初始化>};派生类构造函数的任务初始化基类数据成员初始化子对象数据成员初始化其它派生类数据成员派生类构造函数执行顺序调用基类构造函数调用子对象构造函数执行派生类构造函数体特
殊
形
式
的
派
生
类
构
造
函
数多层派生时,不需要在成员初始化表中理出其上每一层派生类的构造函数,只需要列出其直接基类的构造函数即可派生类新增成员无需任何初始化时,派生类构造函数的函数体唯恐如果基类没有定义构造函数,或者定义了没有参数的构造函数,派生类定义构造函数可以不写基类构造函数,派生类构造函数调用时,系统会自动首先调用基类的默认构造函数派
生
类
的
析
构
函
数回顾:析构函数的作用在对象撤销之前,进行必要的清理工作派生类的析构函数对派生类新增加的成员进行清理根据需要定义相应的析构函数如果有子对象,还需要对子对象进行清理调用子对象的析构函数完成需要对接收自基类的成员进行清理调用基类的析构函数完成派生类析构函数的任务清理基类数据成员清理子对象数据成员清理其它派生类数据成员派生类析构函数执行顺序调用基类析构函数调用子对象析构函数执行派生类析构函数部分派
生
类
的
同
名
覆
盖覆盖规则基类的同名成员在派生类中被屏蔽,成为不可见的定义在派生类对象模块中通过对象名访问同名的成员,访问的是派生类的成员实例:studentstudent_A;undergstudentstudent_B;student_A.print();//调用基类中的成员函数printstudent_B.print();//调用派生类中的成员函数printstudent_B.student::print();//指明作用域而调用基类成员数据成员的覆盖只要命名相同即可成员函数的覆盖不仅函数名要相同,函数的参数表包括参数个数和类型都要相同多
重
继
承一个类可以从一个或者多个基类派生而来。根据派生类继承基类的个数,将继承分为单继承和多继承。当派生类有多个基类时称为多继承。单继承可以看作是多继承的一个特例,多继承可以看作是多个单继承的组合,它们有很多相同特性。实例教师类在职研究生类研究生类多
重
继
承
派
生
类
的
定
义class<派生类名>:<继承方式><基类名1>,…,<继承方式><基类名n>{<派生类新定义成员>};多
重
继
承
派
生
类
的
构
造
函
数成员初始化表中需要包含多个基类构造函数一般形式<派生类名>(<总参数表>):<基类名1>(<参数表1>),…,<基类名n>(<参数表n>){<派生类数据成员的初始化>};多
重
继
承
的
二
义
性
问
题情形一:两个基类有同名成员classgstudent:
{protected:intID;char*name;char*major;public:init();……}classteacher
{protected:intID;char*name;
char*title;public:init();……}classonjobgstudent:publicgstudent,publicteacher
{public:voidprint();{cout<<ID<<endl;cout<<name<<endl;}……}main()
{onjobgstudentTeach_A;
Teach_A.init();//该调用哪个init()?
Teach_A.print();//print函数该输出哪个ID…?//研究生学号和教师工号并不同}二义性解
决
方
案
一用作用域运算符“::”进行限定,显式访问基类成员。main()
{onjobgstudentTeach_A;
Teach_A.Teacher.init();Teach_A.gstudent.init();
Teach_A.print();}classonjobgstudent:publicgstudent,publicteacher
{public:voidprint();{cout<<Teacher.ID<<endl;cout<<T<<endl;cout<<gstudent.ID<<endl;cout<<major<<endl;}……}派生类的成员函数访问基类成员,不必写对象名情
形
二:两个基类和派生类都有同名成员classgstudent:
{protected:intID;char*name;char*major;public:voidinit(…);……}classteacher
{protected:intID;char*name;
char*title;public:voidinit(…);……}classonjobgstudent:publicgstudent,publicteacher
{public:voidinit(…);{
}voidprint();……}main()
{onjobgstudentTeach_A;
Teach_A.init(…);//调用哪个init?
//根据同名覆盖原则//调用onjobgstudent的init
Teach_A.print();}无二义性内在问题分析Teacher_A.init调用类onjobgstudent的init函数进行初始化,而类onjobgstudent的init函数分别调用了gstudent的init函数和teacher的init函数进行基类数据成员的初始化Teacher_A这个对象中保留了多份同名成员teacher.IDgstudent.IDteacher.ID和gstudent.ID互不相同必须保留
和相同保留多份同名数据占用空间,易出错解
决
方
案
二:虚
基
类
的
引
入派生类:在职研究生类直接基类:教师类、研究生类直接基类来自于同一个基类(共同基类):Person类教师类和研究生类
具有数据成员ID,不再具有nameperson类具有数据成员name情况是否有改善?教师类继承person类的name研究生类继承person类的name在职研究生类中仍有两个同名name数据备份教师类在职研究生类研究生类Person类publicpublicpublicpublic虚
基
类
的
作
用
和
声
明将Person类声明为虚基类使在职研究生类在多重继承来自共同基类的直接基类时只保留一份同名成员在职研究生类中只有一份name数据,来自于间接基类Person类虚基类的声明教师类在职研究生类研究生类Person类virtualpublicvirtualpublicpublicpublicclassperson{…}//正常申明classteacher:virtualpublicperso{…}classgstudent:virtualpublicperson{…}classonjobgstudent:publicteacher,publicgstudent注
意
事
项虚基类在声明派生类时,通过制定继承方式时声明class派生类名
:virtual继承方式
虚基类名虚基类需要在其所有直接派生类中声明为虚基类person类为虚基类,其直接派生类teacher和gstudent均需声明person为虚基类,缺一不可课
后
思
考
题1、目前情况下,类teacher和类gstudent仍然有同名数据成员:ID,请问类onjobgstuent如何处理该同名数据成员?2、如果person类中也增加一个数据成员为ID用于存储每个人的身份证号,又会出现什么情况?虚
基
类
的
初
始
化引入虚基类之前的情况在职研究生类的构造函数进行初始化时,调用教师类和研究生类各自的构造函数进行初始化,而教师类和研究生类各自的构造函数又分别去调用person类的构造函数在职研究生类的构造函数中只需要写出它的直接基类teacher和gstudent的构造函数即可教师类在职研究生类研究生类Person类publicpublicpublicpublic引
入
虚
基
类
后,……在职研究生类中只保留了一份同名数据name同名数据来自虚基类Person类这一份数据成员的初始化必须由派生类直接完成如果沿用原来的方式,共同基类person的初始化由teacher和gstudent分别调用,而进行了多次初始化不合理教师类在职研究生类研究生类Person类virtualpublicvirtualpublicpublicpublic虚
基
类
初
始
化
的
规
定最后的派生类不仅要负责对其直接基类进行初始化,还需要负责虚基类的初始化onjobgstudent(intt_n,ints_n,stringfullname,stringT_title,stringS_major):person(fullname),gstudent(s_n,S_major,fullname),teacher(t_n,T_tile,fullname)虚
基
类
初
始
化
的
说
明C++编译系统只执行最后派生类对虚基类的构造函数的调用,而忽略虚基类的其它派生类对虚基类的构造函数的调用,保证虚基类的数据成员不会被多次初始化构造函数的调用顺序(1)先调用虚基类的构造函数,再调用非虚基类的构造函数(2)若同一层次中包含多个虚基类,其调用顺序为定义时的顺序(3)若虚基类由非虚基类派生而来,则仍按先调用基类构造函数,再调用派生类构造函数的顺序引
入
继
承
后
的
解
决
方
案(俄
罗
斯
方
块)classShape{intcolor;//基类成员voidleftshift(){};……};classL-shape:publicshape//方块类派生了L型方块类//L型方块类继承了方块类,自动拥有其成员{charL-box[2];//定义L型方块类的形状属性voidleftshift(){……};……};不
足
之
处基类的leftshift()等成员函数的函数体均为空,在实现部分仍要写出函数体,显得冗余,本无必要,但为了统一规范类簇的基本行为,又不得不如此主控程序每次随机生成7种方块中的一种,然后响应键盘控制完成相应的操作和显示,由于7种方块对象分属于7种方块派生类创建,尽管主控程序中方块对象的行为和操作保持一致,仍然需要将相似的代码重复7遍,以保证调用和方块对象一致的派生类中的成员函数while(;;){inti=random()%7;ifi=0{createaL-shapeobject;detectkeyboard;operateL-shapeobject;display;rule-control;…}ifi=1{createa1-shapeobject;detectkeyboard;operate1-shapeobject;display;rule-control;…}….}程序不够简洁Tobecontinued——多态性和虚函数多
态
性
和
虚
函
数问
题
的
分
析
有一个类簇,一个基类派生了不同的派生类,这个类簇具有统一的基本行为function,但是不同的派生类实现function的方法不统一不同的派生类创建了一组不同的对象,这一组对象,接收到相同的信息,都需要完成function行为,但是由于对象由不同的派生类所创建,不同派生类完成类似功能的方法不同,需要分别调用不同内容的函数来完成目前的解决机制既然function是这个类簇的统一的基本行为,所以基类中定义统一的成员函数funciton不同的派生类定义各自同名的成员函数function,根据同名覆盖原则屏蔽基类的成员函数,不同派生类创建的对象调用各自对应的成员函数实
例classstudent:
{protected:intID;char*name;public:voidinit(…);voidprint(…);……}classbachelor:publicstudent
{protected:char*major;
floatscore[40];public:voidinit(…);voidprint(…);……}classmaster:publicstudent,{protected:char*major;teacheradvisor;//导师
floatscore[20];public:voidinit(…);voidprint(…);……}classdoctor:publicstudent,{protected:char*major;teacheradvisor[5];//导师组
floatscore[10];public:voidinit(…);voidprint(…);……}需要打印所有学生的学籍情况bachelorstu1;…stu1.print();…masterstu2;stu2.print();…doctorstu3…stu3.print();…主函数中建立了3个不同类的对象,进行了类似的操作,重复写了3遍类似的语句繁琐,不够简洁理想状态student*stu[3]={&stu1,&stu2,&stu3};//声明基类指针数组for(inti=0;i<3;i++){…stu[i]->print();/*单一指令,希望根据对象的类型调用对应派生类的特定函数*/…}对象stu1,stu2,stu3来自于同一个基类student,而基类与派生类对象间遵循类型兼容规则事实上,由于stu[0],stu[1]和stu[2]只能访问基类成员,所以调用的都是student类中的print函数解
决
机
制动态多态相对于静态多态而言:函数重载、运算符重载特点:程序编译时知道调用哪个函数(编译时的多态性)编译时不能确定调用那个函数,只有在程序运行是才能动态确定操作所针对的对象理想状态下,stu[i].print()调用哪个函数取决于运行时i的值运行时的多态性引入虚函数虚
函
数虚函数是一类特殊的基类成员函数在基类中声明,类内用virtual声明在派生类中可重新定义,但函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类需要重新定义函数体派生类中没有重新定义时,派生类简单继承其直接基类的虚函数虚函数是C++语言的多态性质和动态绑定的关键虚
函
数
的
特
性特点一个成员函数被申明为虚函数后,同一类簇内所有类内不能再定义与该虚函数具有相同参数和函数返回值类型的同名非virtual函数一个函数一旦被声明为虚函数,则无论声明它的类被继承了多少层,在每一层派生类中该函数都保持虚函数特性。因此,在派生类中重新定义该函数时,可以省略关键字virtual。但是,为了提高程序的可读性,往往不省略当有虚函数声明时,virtual关键字只用在虚函数的声明中,不能用在虚函数定义中虚
函
数
的
作
用作用可以通过基类指针或者引用来访问基类和派生类中的同名函数基类指针指向某一派生类对象(符合赋值兼容原则),调用指针指向的派生类对象中的函数而非基类或者其它派生类中的函数(虚函数的作用)突破了原来急了指针仅仅只能指向派生类对象中的基类部分的限制实
例classstudent:
{protected:intID;char*name;public:voidinit(…);
virtualvoidprint(…);……}student*stu[3]={&stu1,&stu2,&stu3};//声明基类指针数组for(inti=0;i<3;i++){…stu[i]->print();/*print已申明为虚函数*///stu[0]将调用bachelor的print//stu[1]将调用master的print//stu[0]将调用doctor的print…}多
态
的
分
类C++中多态的分类重载多态:函数重载和运算符重载强制多态:强制类型转换包含多态:虚函数参数多态:函数模板和类模板多
态
的
编
译
实
现编译系统要根据已有的信息,对于同名函数的调用做出判断,确定调用搞得是哪个函数关联/binding/联编/绑定静态多态静态关联、早期关联动态多态动态关联、滞后关联C++规定,动态关联通过继承和虚函数来实现虚函数申明注意事项静态成员函数不能声明为虚函数。因为静态成员函数不属于某一个对象,没有多态性的特征构造函数不能是虚函数内联成员函数不能声明为虚函数。因为内联函数的执行代码是明确的,在编译时已被替换,没有多态性的特征析构函数可以是虚函数,且往往被定义为虚函数。一般来说,若某类中有虚函数,则其析构函数也应当定义为虚函数虚
析
构
函
数申明格式virtual~<类名>();必要性实例若析构函数并非虚函数stu是一个指向基类的指针变量,指向new开辟的空间new开辟的空间是按照派生类doctor开辟的,所以除了name、ID之外,还为major、advisor、score申请了空间deletestu仅仅调用了基类student的析构函数,仅仅释放name、ID的空间,而没有释放major、advisor、score的空间student*stu=newdoctor;//声明基类指针指向doctor类……stu->print();/*print已申明为虚函数*///stu将调用doctor的printdeletstu;return0;纯
虚
函
数实例分析福州大学中的学生总共就3类(bachelor、master、doctor),它们的共有基类student实际上是不需要实例化为任何对象的,因此student其实无需为虚函数print定义具体的实现尽管基类本身不需要某一个成员函数,但是考虑到派生类的需要,可以在基类中预留一个函数名,将其声明为纯虚函数只给出该虚函数的原型,但不需要给出函数体virtual
函数类型
函数名(参数列表)const=0;具体功能留给派生类根据需要去定义包含纯虚函数的类称为抽象类抽
象
类包含纯虚函数的类是无法创建对象的纯虚函数是不能被调用的抽象类是不能用来定义对象的专门作为基类派生新类抽象类的主要作用是将有关的派生类组织在一个继承层次结构中,由抽象类为它们提供一个公共的根,相关的派生类就从这个根派生出来可以定义指向抽象类数据的指针变量(多态的实现)继
承
和
多
态
使
用
实
例实
例
问
题
描
述P.393先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“<<”和“>>”,使之能用于输出以上类对象基于继承的解决方案基于继承和多态的解决方案基
类Point的
设
计
与
实
现#include<iostream>//声明类PointclassPoint{public:Point(floatx=0,floaty=0);//有默认参数的构造函数
voidsetPoint(float,float);//设置坐标值
floatgetX()const{returnx;}//读x坐标
floatgetY()const{returny;}//读y坐标
friendostream&operator<<(ostream&,constPoint&);//重载运算符“<<”protected://受保护成员
floatx,y;};//下面定义Point类的成员函数//Point的构造函数Point::Point(floata,floatb)//对x,y初始化{x=a;y=b;}//设置x和y的坐标值voidPoint::setPoint(floata,floatb)//为x,y赋新值{x=a;y=b;}//重载运算符“<<”,使之能输出点的坐标ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″<<endl;returnoutput;}派
生
类Circle的
设
计
与
实
现classCircle:publicPoint//circle是Point类的公用派生类{public:Circle(floatx=0,floaty=0,floatr=0);//构造函数
voidsetRadius(float);//设置半径值
floatgetRadius()const;//读取半径值
floatarea()const;//计算圆面积
friendostream&operator<<(ostream&,constCircle&);//重载运算符“<<”private:floatradius;};//定义构造函数,对圆心坐标和半径初始化Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}//设置半径值voidCircle::setRadius(floatr){radius=r;}//读取半径值floatCircle::getRadius()const{returnradius;}//计算圆面积floatCircle::area()const{return3.14159*radius*radius;}//重载运算符“<<”,使之按规定的形式输出圆的信息ostream&operator<<(ostream&output,constCircle&c){output<<″Center=[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius<<″,area=″<<c.area()<<endl;returnoutput;}派
生
类Cylinder的
设
计
与
实
现classCylinder:publicCircle//Cylinder是Circle的公用派生类{public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);//构造函数
voidsetHeight(float);//设置圆柱高
floatgetHeight()const;//读取圆柱高
floatarea()const;//计算圆表面积
floatvolume()const;//计算圆柱体积
friendostream&operator<<(ostream&,constCylinder&);//重载运算符“<<”protected:floatheight;//圆柱高};//定义构造函数Cylinder::Cylinder(floata,floatb,floatr,floath):Circle(a,b,r),height(h){}voidCylinder::setHeight(floath){height=h;}//设置圆柱高floatCylinder::getHeight()const{returnheight;}//读取圆柱高floatCylinder::area()const//计算圆表面积{return2*Circle::area()+2*3.14159*radius*height;}floatCylinder::volume()const//计算圆柱体积{returnCircle::area()*height;}//重载运算符“<<”ostream&operator<<(ostream&output,constCylinder&cy){output<<″Center=[″<<cy.x<<″,″<<cy.y<<″],r=″<<cy.radius<<″,h=″<<cy.height<<″\\narea=″<<cy.area()<<″,volume=″<<cy.volume()<<endl;returnoutput;}抽
象
基
类shape的
设
计
与
实
现#include<iostream>usingnamespacestd;//声明抽象基类ShapeclassShape{public:virtualfloatarea()const{return0.0;}//虚函数
virtualfloatvolume()const{return0.0;}//虚函数
virtualvoidshapeName()const=0;//纯虚函数};Point类
的
设
计
与
实
现//声明Point类classPoint:publicShape//Point是Shape的公用派生类{public:Point(float=0,float=0);voidsetPoint(float,float);floatgetX()const{returnx;}floatgetY()const{returny;}virtualvoidshapeName()const{cout<<″Point:″;}//对虚函数进行再定义
friendostream&operator<<(ostream&,constPoint&);protected:floatx,y;}//定义Point类成员函数Point::Point(floata,floatb){x=a;y=b;}voidPoint::setPoint(floata,floatb){x=a;y=b;}ostream&operator<<(ostream&output,constPoint&p){output<<″[″<<p.x<<″,″<<p.y<<″]″;returnoutput;}Circle类
的
设
计
与
实
现//声明Circle类classCircle:publicPoint{public:Circle(floatx=0,floaty=0,floatr=0);voidsetRadius(float);floatgetRadius()const;virtualfloatarea()const;virtualvoidshapeName()const{cout<<″Circle:″;}//对虚函数进行再定义
friendostream&operator<<(ostream&,constCircle&);protected:floatradius;};//声明Circle类成员函数Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}voidCircle::setRadius(floatr):radius(r){}floatCircle::getRadius()const{returnradius;}floatCircle::area()const{return3.14159*radius*radius;}ostream&operator<<(ostream&output,constCircle&c){output<<″[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius;returnoutput;}Cylinder类
的
设
计
与
实
现//声明Cylinder类classCylinder:publicCircle{public:Cylinder(floatx=0,floaty=0,floatr=0,floath=0);voidsetHeight(float);virtualfloatarea()const;virtualfloatvolume()const;
virtualvoidshapeName()const{cout<<″Cylinder:″;}//对虚函数进行再定义fri
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度数字经济投资入股协议书
- 二零二五年度个人手房车位产权转让及车位使用权及车位租赁合同
- 2025形婚协议书模板与婚后共同生活纠纷调解服务合同
- 二零二五年度海洋资源开发项目终止合作备忘录
- 二零二五年度户外休闲用品区域独家代理合作协议
- 2025年度股权内部转让与公司风险控制协议
- 二零二五年度夫妻共同生活费用分担协议
- 个人运输合同书(2025年度跨境物流)
- 2025年度知识产权居间许可合同
- 2024年六年级英语上册模拟考试
- 非线性弹性本构关系全量型增量型③弹塑性本构课件
- 涉嫌虚假诉讼立案监督申请书
- 通用高考英语答题卡模板word模板
- 外文文献 A SP的网站新闻管理系统的设计与实现 中英文版
- 中国教育简史全书ppt完整版课件最全电子教案正本书教学教程
- 中学生心理健康诊断测验-MHT量表
- 年轻干部教育管理监督调研情况报告
- 三级安全管理标准化评定标准
- 血气分析报告解读PPT课件(PPT 33页)
- 简谱视唱15942
- 民宿设计要点--ppt课件
评论
0/150
提交评论