2011级面向对象程序设计part iii_第1页
2011级面向对象程序设计part iii_第2页
2011级面向对象程序设计part iii_第3页
2011级面向对象程序设计part iii_第4页
2011级面向对象程序设计part iii_第5页
已阅读5页,还剩164页未读 继续免费阅读

下载本文档

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

文档简介

1、PART III面 向 对 象 的 程 序 设 计引引 言言概 要 回 顾基于过程的程序设计基于对象的程序设计面向对象的程序设计 类的生成、设计类对象的使用C+过程化语言基础问 题 的 回顾 俄罗斯方块的设计与实现 基于过程的设计与实现 基于对象的设计与实现 方块对象:七种方块对象 图框对象:负责绘制俄罗斯方块运行的图框界面 判定对象:负责消行、统计分数、方块到顶游戏终结基 于 对 象 的 解 决 方 案 基 于 对 象 的 解 决 方 案 分 析 七种方块: 公有属性:颜色、速度、 不同属性:形状、 共同操作:右移、左移、下移、变形 各种操作由于形状的不同而有不同的实现 这7种方块的移动和变

2、形的实现方法不同,显示也不同,因此不能用同一个类来描述 设计7个类分别描述7种方块 7个方块类中又有很多相同的,如颜色等数据属性,都要四种移动操作,只是各自的实现方法有所不同问题1:共同的数据属性在7个类中各自描述一次,显得繁琐,重复,能够简化为统一描述?问题2:相同的操作能否在统一描述的同时方便地根据方块类型调用不同的实现? 继承和多态结 论 仅仅支持基于类的设计、生成和类对象的使用是不能称为面向对象的程序设计 面向对象程序设计的四大主要特点 抽象 封装 继承 多态基于对象的程序设计面向对象的程序设计PARTIII 纲 要010102020303继承与派生(重点)继承与派生(重点)多态性与虚

3、函数(重点)多态性与虚函数(重点)输入输出流(常用)输入输出流(常用)0404C+ C+ 工具工具继继 承承 与与 派派 生生问 题 回 顾 与 分 析 共同的数据属性在7个方块类中各自描述一次,显得繁琐,重复,能够简化为统一描述? 分析 先统一描述所有方块的共性以及对全体方块的处理功能;描述某一种方块时,首先说明它是其中之一,然后再逐一描述这种方块的个性 面向对象的程序设计中类的继承与派生继 承 与 派 生类类 的的 继继 承承派生类可以获得基类的已有派生类可以获得基类的已有特性特性派生类继承了基类的所有数派生类继承了基类的所有数据成员和成员函数据成员和成员函数类的继承是层次结构的类的继承是

4、层次结构的基类是派生类的抽象基类是派生类的抽象类的派生类的派生基类产生了一个和具有基类基类产生了一个和具有基类各种特性的新的子类各种特性的新的子类派生类可以对成员作必要的派生类可以对成员作必要的增加和调整增加和调整派生类又可以作为基类再派派生类又可以作为基类再派生出新的派生类生出新的派生类派生类是基类的具体化派生类是基类的具体化继承派生在一个已经存在的类(基类在一个已经存在的类(基类/ /父类)的基础之上父类)的基础之上建立一个新的类(派生类建立一个新的类(派生类/ /子类)子类)继 承 与 派 生 的 基 本 形 式单 继 承 和 多 重 继 承 单继承 一个派生类只能由一个基类派生而来 一

5、个子类只有一个父类 多重继承 一个派生类由多个(=2)基类派生而成 一个子类有两个或者两个以上的父类派 生 类 的 声 明 一般形式class : ;派 生 类 实 例基类(普通学生类)基类(普通学生类)class student private: int ID; char *name; float score11; public: student(int n1,char*nm1) student()delete name; void print() . void get_name() ;派生类声明(大学生类)派生类声明(大学生类)class undergstudent : public :

6、public studentstudent private: char *major; /新增加的数据成员 float score40; public: void get_data print_major( ) /新增加的成员函数 void print () 基类(普通学生类)基类(普通学生类)派生类声明(大学生类)派生类声明(大学生类)大学生类(未考虑访问属性)大学生类(未考虑访问属性) int ID; int ID; char char * *name;name; float score40;float score40; char char * *major;major; print_ma

7、jor( ) print_major( ) void print() .void print() . . .派 生 类 的 构 成 两大部分两大部分 从基类继承来的成员从基类继承来的成员 声明派生类时增加的部分声明派生类时增加的部分 每一部分都分别包括数据成员和成员函数每一部分都分别包括数据成员和成员函数 构成方式构成方式 并非基类的成员和派生类增加成员的简单加和并非基类的成员和派生类增加成员的简单加和 接收基类的全部成员接收基类的全部成员 调整从基类接收的成员调整从基类接收的成员 声明派生类时增加的成员声明派生类时增加的成员 根据需要增加成员根据需要增加成员 定义派生类的构造函数和定义派生类

8、的构造函数和析构析构函数函数(不能从基类继承而来)(不能从基类继承而来)基 类 成 员 的 接 收 和 调 整 无条件地全部接收无条件地全部接收 不接收析构函数和构造函数不接收析构函数和构造函数 接收到的基类成员可以调整接收到的基类成员可以调整 通过指定继承方式改变成员的访问通过指定继承方式改变成员的访问属性属性 可以声明同名成员加以覆盖可以声明同名成员加以覆盖派 生 类 的 继 承 方 式 三种继承方式 public private protected 不同的继承方式决定了基类成员在派生类中访问属性派 生 类 成 员 的 访 问 属 性 分情况处理 基类的成员函数访问基类成员 派生类成员函数

9、访问派生类自己增加的成员 基类的成员函数访问派生类的成员 派生类外访问派生类的成员 根据成员的访问属性判定能否由类外访问该成员 派生类的成员函数访问基类的成员 派生类外访问基类的成员 核心问题:如何确定基类成员在派生类中的访问属性 基类成员声明的访问属性 派生类对基类的继承方式三 种 继 承 方 式 下 派 生 类 中基 类 成 员 的 访 问 控 制 权 限 继承继承 方式方式基类基类成员成员公有继承公有继承私有继承私有继承保护继承保护继承公有成员公有成员公有公有私有私有保护保护私有成员私有成员派生类成员不派生类成员不可访问可访问派生类成员不派生类成员不可访问可访问派生类成员不派生类成员不可

10、访问可访问保护成员保护成员保护保护私有私有保护保护公 有 继 承基类的私有成员并没有成为派生类的私有成员基类的私有成员仅仅只有基类的成员函数才能应用基类的私有成员是派生类的不可访问的成员基类的私有成员只能通过基类的公有成员函数加以访问实例:基类student派生类undergstudent(公有继承)假设一创建一个对象freshman是一个大一新生(Bob,10009,CS, )如何能够正确获取name、ID、major等信息?方 案 一 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:coutname;coutID;coutmajor;原因:nam

11、e和ID是基类的私有成员,在派生类中不可访问方 案 二 基类中公有成员函数get_namecoutname; 基类中公有成员函数get_IDcoutID; 派生类中公有成员函数get_majorcoutmajor; 主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取 公有继承下,基类的公有成员在派生类中仍为公有,可在派生类外访问方 案 三 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:get_name; /调用基类的成员函数get_ID;/调用基类的成员函数coutmajor; 基类中公有成员函数ge

12、t_name定义如下:coutname; 基类中公有成员函数get_ID定义如下:cout set_major(“EE”);/*不能访问派生类增加的成员私 有 继 承私有基类的公有成员和保护成员相当于派生类中的私有成员派生类的成员可以访问私有基类的公有成员和保护成员,但是派生类外不能访问实例:情况与前述相同,唯一的区别是派生类undergstudent以私有继承的方式由student类派生而成方 案 一 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:coutname;coutID;coutmajor;原因:name和ID是基类的私有成员,公有继承下

13、在派生类中已不可访问,私有继承下依然不可行方 案 二 基类中公有成员函数get_namecoutname; 基类中公有成员函数get_IDcoutID; 派生类中公有成员函数get_majorcoutmajor; 主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取 原因:私有继承下,基类的公有成员等同于派生类的私有成员,无法在派生类外访问方 案 三 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:get_name; /调用基类的成员函数get_ID;/调用基类的成员函数coutmajor; 基类中公有成

14、员函数get_name定义如下:coutname; 基类中公有成员函数get_ID定义如下:coutID;保 护 继 承 保护成员 受保护的成员不能为类外访问,但是可以被派生类的成员函数所引用基类(普通学生类)class student private: int ID; char *name; float score11; public: ;派生类声明(大学生类)class undergstudent : public : public studentstudent private: char *major; /新增加的数据成员 float score40; public: class stu

15、dent protected: int ID; char *name; float score11; public: ;保 护 继 承保护基类的公有成员和保护成员相当于派生类中的保护成员派生类的成员可以访问保护基类的公有成员和保护成员,但是派生类外不能访问实例:情况与前述相同,但是派生类undergstudent以保护继承的方式由student类派生而成第 一 种 情 形(ID 和name为私有数据) 基类(普通学生类)class student private:private: int ID; char *name; float score11; public: student(int n1

16、,char*nm1) student()delete name; void print() . void get_name() ;派生类声明(大学生类)class undergstudent : protected : protected studentstudent private: char *major; /新增加的数据成员 float score40; public: void get_data print_major( ) /新增加的成员函数 void print () 方 案 一 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:coutn

17、ame;coutID;coutmajor;原因:name和ID是基类的私有成员,保护继承下在派生类中依然不可访问方 案 二 基类中公有成员函数get_namecoutname; 基类中公有成员函数get_IDcoutID; 派生类中公有成员函数get_majorcoutmajor; 主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取 原因:保护继承下,基类的公有成员等同于派生类的保护成员,无法在派生类外访问方 案 三 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:get_name; /调用基类的成员函

18、数get_ID;/调用基类的成员函数coutmajor; 基类中公有成员函数get_name定义如下:coutname; 基类中公有成员函数get_ID定义如下:coutID;第二种情形 (ID和name为保护成员数据) 基类(普通学生类)class student protected:protected: int ID; char *name; private: float score11; public: student(int n1,char*nm1) student()delete name; void print() . void get_name() ;派生类声明(大学生类)cla

19、ss undergstudent : protected : protected studentstudent private: char *major; /新增加的数据成员 float score40; public: void get_data print_major( ) /新增加的成员函数 void print () 方 案 一 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:coutname;coutID;coutmajor;原因:name和ID是基类的保护成员,公有继承下和保护继承下派生类中可以访问方 案 二 基类中公有成员函数get_n

20、amecoutname; 基类中公有成员函数get_IDcoutID; 派生类中公有成员函数get_majorcoutmajor; 主函数中分别调用freshman的get_name、get_ID和get_major完成信息的获取 原因:保护继承下,基类的公有成员等同于派生类的保护成员,无法在派生类外访问方 案 三 主函数调用freshman的公有成员函数get_data 派生类中get_data如下定义:get_name; /调用基类的成员函数get_ID;/调用基类的成员函数coutmajor; 基类中公有成员函数get_name定义如下:coutname; 基类中公有成员函数get_ID

21、定义如下:coutID;保 护 成 员 和 保 护 继 承 的 效 用 在类的在类的层次继承结构层次继承结构中找到数据中找到数据共享与成员隐蔽之间的最佳均衡共享与成员隐蔽之间的最佳均衡 如果需要在派生类中引用基类的如果需要在派生类中引用基类的某些成员,应当将基类中的这些某些成员,应当将基类中的这些成员声明为成员声明为protectedprotected,而非,而非privateprivate 以student类中的ID和name为例 如果声明成为private,将造成派生类无法访问的局面,使得派生类的使用很不方便 如果声明成为public,使得基类的数据隐蔽性受到一定的损伤 如果声明成为pro

22、tected,派生类可以访问从而使得基类和派生类之间可以实现数据共享,基类和派生类的外部都不能访问,保障了数据的隐蔽性多 级 派 生 下 的 访 问 属 性 类的层次继承结构导致了类的多级派生 基类 基类的派生类 派生类的派生类 直接派生类 vs. 间接派生类 直接基类 vs. 间接基类 多级派生情况下各成员的访问属性 在基类和其直接派生类之间按照一级派生访问属性的原则确定各成员的访问属性后逐级向下迁移派 生 类 的 构 造 函 数 回顾:构造函数的作用 对类中的数据成员进行初始化 派生类的构造函数的特殊性 派生类的数据成员构成较为特殊 从基类中接收来的数据成员 派生类自己增加的数据成员 派生

23、类并没有继承基类的构造函数 派生类的构造函数的初始化工作 基类数据成员的初始化工作 派生类增加的数据成员的初始化工作派 生 类 实 例基类(普通学生类)基类(普通学生类)class student private: int ID; char *name; float score11; public: student(int n1,char*nm1) student()delete name; void print() . void get_name() ;派生类声明(大学生类)派生类声明(大学生类)class undergstudent : public : public studentstu

24、dent private: char *major; /新增加的数据成员 float score40; public: void get_data print_major( ) /新增加的成员函数 void print () 实 例:大学生类undergstudent的构造函数方案一undergstudentundergstudent(int n, string stu_name, string (int n, string stu_name, string stu_major, float stu_major, float * * scoreList) scoreList) ID = n;

25、ID = n; name= stu_name; name= stu_name; major = stu_major; major = stu_major; score = scoreList; score = scoreList; 派生类无法访问基类的私有成员数派生类无法访问基类的私有成员数据据方案二undergstudentundergstudent(int n, string stu_name, string (int n, string stu_name, string stu_major, float stu_major, float * * scoreList) scoreList)

26、 student(n, stu_name); student(n, stu_name); / /调用基类构造函数调用基类构造函数 major = stu_major; major = stu_major; score = scoreList; score = scoreList; 不能在派生类构造函数体中显式调不能在派生类构造函数体中显式调用构造函数!虽然能通过编译,但用构造函数!虽然能通过编译,但结果不对结果不对派 生 类 构 造 函 数 的 解 决 之 道方案三undergstudentundergstudent(int n, string stu_name, string (int n,

27、 string stu_name, string stu_major, float stu_major, float * * scoreList) scoreList): student(n, stu_name);: student(n, stu_name);/调用基类构造函数调用基类构造函数 / /* *派生类的函数体中只对派生类派生类的函数体中只对派生类新增的数据成员进行初始化新增的数据成员进行初始化* */ / major = stu_major; major = stu_major; score = scoreList; score = scoreList; 解决的办法是通过成员初始化

28、表来完成基类数据成员的初始化,在成员初始化表中可以显式调用基类构造函数派生类的函数体完成新增数据成员的初始化派 生 类 构 造 函 数 的 基 本 形 式() :();说明:1、如果派生类构造函数在类的外面定义,类体中只需要写这个函数的声明:() ;2、总参数表中的参数包括了基类构造函数所需的参数和派生类新增数据成员初始化所需的参数 生成一个类对象(10009,bob,CS,) undergstudent(n, stu_name, stu_major, scorelist):student(n, stu_name)构造函数的成员初始化表有 子 对 象 的 派 生 类 的 构 造 函 数 子对象

29、 类的数据成员本身就是一个类对象class undergstudent : public : public studentstudent private: char *major; float score40; Teacher* tutor; /子对象 public: class teacher private: int ID; char* name; char* title; public: 分析: 对象建立时需要对它的数据成员进行初始化 派生类构造函数对其数据成员进行初始化的时候也需要对其中的子对象进行初始化 在成员初始化表中显式调用子对象的构造函数undergstudent (int n

30、, string stu_name, string stu_major, float * scoreList, int T_n, string T_name, string T_title): student(n, stu_name), tutor (T_n, T_name, T_title)派 生 类 构 造 函 数 的 一 般 形 式():(), ();派生类构造函数的任务派生类构造函数的任务初始化基类数据成员初始化基类数据成员初始化子对象数据成员初始化子对象数据成员初始化其它派生类数据成员初始化其它派生类数据成员派生类构造函数执行顺序派生类构造函数执行顺序调用基类构造函数调用基类构造函数

31、调用子对象构造函数调用子对象构造函数执行派生类构造函数体执行派生类构造函数体特 殊 形 式 的 派 生 类 构 造 函 数 多层派生时,不需要在成员初始化表中理出其上每一层派生类的构造函数,只需要列出其直接基类的构造函数即可 派生类新增成员无需任何初始化时,派生类构造函数的函数体唯恐 如果基类没有定义构造函数,或者定义了没有参数的构造函数,派生类定义构造函数可以不写基类构造函数,派生类构造函数调用时,系统会自动首先调用基类的默认构造函数派 生 类 的 析 构 函 数 回顾:析构函数的作用 在对象撤销之前,进行必要的清理工作 派生类的析构函数 对派生类新增加的成员进行清理 根据需要定义相应的析构

32、函数 如果有子对象,还需要对子对象进行清理 调用子对象的析构函数完成 需要对接收自基类的成员进行清理 调用基类的析构函数完成派生类析构函数的任务派生类析构函数的任务清理基类数据成员清理基类数据成员清理子对象数据成员清理子对象数据成员清理其它派生类数据成员清理其它派生类数据成员派生类析构函数执行顺序派生类析构函数执行顺序调用基类析构函数调用基类析构函数调用子对象析构函数调用子对象析构函数执行派生类析构函数部分执行派生类析构函数部分派 生 类 的 同 名 覆 盖 覆盖规则 基类的同名成员在派生类中被屏蔽,成为不可见的 定义在派生类对象模块中通过对象名访问同名的成员,访问的是派生类的成员 实例:st

33、udent student_A;undergstudent student_B;student_A.print();/调用基类中的成员函数printstudent_B.print();/调用派生类中的成员函数printstudent_B.student:print();/指明作用域而调用基类成员数据成员的覆盖只要命名相同即可成员函数的覆盖不仅函数名要相同,函数的参数表包括参数个数和类型都要相同多 重 继 承 一个类可以从一个或者多个基类派生而来。根据派生类继承基类的个数,将继承分为单继承和多继承。 当派生类有多个基类时称为多继承。单继承可以看作是多继承的一个特例,多继承可以看作是多个单继承的组

34、合,它们有很多相同特性。 实例教师类在职研究生类研究生类多 重 继 承 派 生 类 的 定 义class : , , ;多 重 继 承 派 生 类 的 构 造 函 数 成员初始化表中需要包含多个基类构造函数 一般形式():(), , () ;多 重 继 承 的 二 义 性 问 题 情形一:两个基类有同名成员class gstudent: protected: int ID; char* name; char* major; public: init(); class teacher protected: int ID; char* name; char* title; public: init

35、(); class onjobgstudent: public gstudent, public teacher public: void print(); cout IDendl; coutnameendl; main() onjobgstudent Teach_A; Teach_A.init(); /该调用哪个init()? Teach_A.print(); /print函数该输出哪个ID? /研究生学号和教师工号并不同二义性解 决 方 案 一用作用域运算符“:”进行限定,显式访问基类成员。main()main() onjobgstudent Teach_A; onjobgstudent

36、Teach_A; Teach_A.Teacher.init();Teach_A.Teacher.init(); Teach_A.gstudent.init(); Teach_A.gstudent.init(); Teach_A.print(); Teach_A.print(); class onjobgstudent: class onjobgstudent: public gstudent, public gstudent, public teacher public teacher public: public: void print(); void print(); cout coutT

37、eacher.IDTeacher.IDendl;endl; cout coutTT endl; endl; cout coutgstudent.IDgstudent.IDendl;endl; cout coutmajormajorendl;endl; 派生类的成员派生类的成员函数访问基类函数访问基类成员,不必写成员,不必写对象名对象名情 形 二:两个基类和派生类都有同名成员class gstudent:class gstudent: protected: protected: int ID; int ID; char char* * name; na

38、me; char char* * major; major; public: public: void init(); void init(); class teacherclass teacher protected: protected: int ID; int ID; char char* * name; name; charchar* * title; title; public public: : void init(); void init(); class onjobgstudent: class onjobgstudent: public gstudent, public gs

39、tudent, public teacher public teacher public: public: void init(); void init(); void print(); void print(); main()main() onjobgstudent Teach_A; onjobgstudent Teach_A; Teach_A.init();Teach_A.init(); / /调用哪个调用哪个initinit? /根据同名覆盖原则根据同名覆盖原则 / /调用调用onjobgstudentonjobgstudent的的initinit Teach_A.print();Tea

40、ch_A.print(); 无二义性 内在问题分析 Teacher_A.init调用类onjobgstudent的init函数进行初始化,而类onjobgstudent的init函数分别调用了gstudent的init函数和teacher的init函数进行基类数据成员的初始化 Teacher_A这个对象中保留了多份同名成员 teacher.ID gstudent.ID teacher.ID 和gstudent.ID互不相同必须保留 和相同保留多份同名数据占用空间,易出错解 决 方 案 二:虚

41、基 类 的 引 入 派生类:在职研究生类 直接基类:教师类、研究生类 直接基类来自于同一个基类 (共同基类):Person类 教师类和研究生类 具有数据成员ID,不再具有name person类具有数据成员name情况是否有改善?教师类继承person类的name研究生类继承person类的name在职研究生类中仍有两个同名name数据备份教师类在职研究生类研究生类Person类publicpublicpublicpublic虚 基 类 的 作 用 和 声 明将Person类声明为虚基类 使在职研究生类在多重继承来自共同基类的直接基类时只保留一份同名成员 在职研究生类中只有一份name数据,来

42、自于间接基类Person类虚基类的声明教师类在职研究生类研究生类Person类virtual publicvirtual publicpublicpublicclass person /正常申明class teacher :virtual public persoclass gstudent :virtual public personclass onjobgstudent : public teacher, public gstudent注 意 事 项 虚基类在声明派生类时,通过制定继承方式虚基类在声明派生类时,通过制定继承方式时声明时声明class 派生类名派生类名 : virtual 继

43、承方式继承方式 虚基类名虚基类名 虚基类需要在其虚基类需要在其所有所有直接派生类中声明为虚直接派生类中声明为虚基类基类person类为虚基类,其直接派生类类为虚基类,其直接派生类teacher和和gstudent均需声明均需声明person为虚基类,缺一为虚基类,缺一不可不可课 后 思 考 题1 1、目前情况下,类、目前情况下,类teacherteacher和类和类gstudentgstudent仍然有同名数据成员:仍然有同名数据成员:IDID,请问类请问类onjobgstuentonjobgstuent如何处理该同如何处理该同名数据成员?名数据成员?2 2、如果、如果personperson

44、类中也增加一个数据类中也增加一个数据成员为成员为IDID用于存储每个人的身份证号,用于存储每个人的身份证号,又会出现什么情况?又会出现什么情况?虚 基 类 的 初 始 化引入虚基类之前的情况 在职研究生类的构造函数进行初始化时,调用教师类和研究生类各自的构造函数进行初始化,而教师类和研究生类各自的构造函数又分别去调用person类的构造函数 在职研究生类的构造函数中只需要写出它的直接基类teacher和gstudent的构造函数即可教师类在职研究生类研究生类Person类publicpublicpublicpublic引 入 虚 基 类 后,在职研究生类中只保留了一份同名数据name 同名数据

45、来自虚基类Person类这一份数据成员的初始化必须由派生类直接完成 如果沿用原来的方式,共同基类person的初始化由teacher和gstudent分别调用,而进行了多次初始化 不合理教师类在职研究生类研究生类Person类virtual publicvirtual publicpublicpublic虚 基 类 初 始 化 的 规 定 最后的派生类不仅要负责对其直接基最后的派生类不仅要负责对其直接基类进行初始化,还需要负责虚基类的类进行初始化,还需要负责虚基类的初始化初始化onjobgstudent (int t_n, int s_n, string fullname, string T_

46、title, string S_major): person (fullname), gstudent (s_n, S_major, fullname), teacher(t_n,T_tile, fullname)虚 基 类 初 始 化 的 说 明 C+编译系统只执行最后派生类对虚基类的构造函数的调用,而忽略虚基类的其它派生类对虚基类的构造函数的调用,保证虚基类的数据成员不会被多次初始化 构造函数的调用顺序(1)先调用虚基类的构造函数,再调用非虚基类的构造函数(2)若同一层次中包含多个虚基类,其调用顺序为定义时的顺序(3)若虚基类由非虚基类派生而来,则仍按先调用基类构造函数,再调用派生类构造函

47、数的顺序引 入 继 承 后 的 解 决 方 案 (俄 罗 斯 方 块)class Shape int color;/基类成员 void leftshift() ; ;class L-shape: public shape /方块类派生了L型方块类 /L型方块类继承了方块类,自动拥有其成员 char L-box2; /定义L型方块类的形状属性 void leftshift() ; ;不 足 之 处 基类的leftshift()等成员函数的函数体均为空,在实现部分仍要写出函数体,显得冗余,本无必要,但为了统一规范类簇的基本行为,又不得不如此 主控程序每次随机生成7种方块中的一种,然后响应键盘控制完

48、成相应的操作和显示,由于7种方块对象分属于7种方块派生类创建,尽管主控程序中方块对象的行为和操作保持一致,仍然需要将相似的代码重复7遍,以保证调用和方块对象一致的派生类中的成员函数while (;) int i =random()%7; if i = 0 create a L-shape object; detect keyboard; operate L-shape object; display; rule-control; if i= 1 create a 1-shape object; detect keyboard; operate 1-shape object; display;

49、rule-control; . 程序不够简洁To be continuedTo be continued多态性和虚函多态性和虚函数数多多 态态 性性 和和 虚虚 函函 数数问 题 的 分 析 有一个类簇,一个基类派生了不同的派生类,这个类簇具有统一的基本行为function,但是不同的派生类实现function的方法不统一 不同的派生类创建了一组不同的对象,这一组对象,接收到相同的信息,都需要完成function行为,但是由于对象由不同的派生类所创建,不同派生类完成类似功能的方法不同,需要分别调用不同内容的函数来完成 目前的解决机制 既然function是这个类簇的统一的基本行为,所以基类中定

50、义统一的成员函数funciton 不同的派生类定义各自同名的成员函数function,根据同名覆盖原则屏蔽基类的成员函数,不同派生类创建的对象调用各自对应的成员函数实 例class student:class student: protected: protected: int ID; int ID; char char* * name; name; public: public: void init(); void init(); void print(); void print(); class bachelor : public studentclass bachelor : publi

51、c student protected: protected: char char* * major; major; float score40;float score40; public public: : void init(); void init(); void print(); void print(); class master: public student, class master: public student, protected: protected: char char* * major; major; teacher advisor;/ teacher adviso

52、r;/导师导师 float score20;float score20; public public: : void init(); void init(); void print(); void print(); class doctor: public student, class doctor: public student, protected: protected: char char* * major; major; teacher advisor5;/ teacher advisor5;/导师组导师组 float score10;float score10; public pub

53、lic: : void init(); void init(); void print(); void print(); 需要打印所有学生的学籍情况bachelor stu1;stu1.print();master stu2;stu2.print();doctor stu3stu3.print();主函数中建立了3个不同类的对象,进行了类似的操作,重复写了3遍类似的语句繁琐,不够简洁 理想状态student *stu3=&stu1,&stu2,&stu3; /声明基类指针数组for(int i=0;iprint(); /*单一指令,希望根据对象的类型调用对应派生类的特定

54、函数*/ 对象stu1,stu2,stu3来自于同一个基类student,而基类与派生类对象间遵循类型兼容规则事实上,由于stu0, stu1和stu2只能访问基类成员,所以调用的都是student类中的print函数解 决 机 制 动态多态 相对于静态多态而言:函数重载、运算符重载 特点:程序编译时知道调用哪个函数(编译时的多态性) 编译时不能确定调用那个函数,只有在程序运行是才能动态确定操作所针对的对象 理想状态下,stui.print()调用哪个函数取决于运行时i的值 运行时的多态性 引入虚函数虚 函 数 虚函数是一类特殊的基类成员函数 在基类中声明,类内用virtual 声明 在派生类

55、中可重新定义,但函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类需要重新定义函数体 派生类中没有重新定义时,派生类简单继承其直接基类的虚函数 虚函数是C+语言的多态性质和动态绑定的关键虚 函 数 的 特 性 特点 一个成员函数被申明为虚函数后,同一类簇内所有类内不能再定义与该虚函数具有相同参数和函数返回值类型的同名非virtual函数 一个函数一旦被声明为虚函数,则无论声明它的类被继承了多少层,在每一层派生类中该函数都保持虚函数特性。因此,在派生类中重新定义该函数时,可以省略关键字virtual。但是,为了提高程序的可读性,往往不省略 当有虚函数声明时,virtual关键

56、字只用在虚函数的声明中,不能用在虚函数定义中虚 函 数 的 作 用 作用 可以通过基类指针或者引用来访问基类和派生类中的同名函数 基类指针指向某一派生类对象(符合赋值兼容原则),调用指针指向的派生类对象中的函数而非基类或者其它派生类中的函数(虚函数的作用) 突破了原来急了指针仅仅只能指向派生类对象中的基类部分的限制实 例class student:class student: protected: protected: int ID; int ID; char char* * name; name; public: public: void init(); void init(); virtu

57、al virtual void print();void print(); student student * *stu3=&stu1,&stu2,&stu3; stu3=&stu1,&stu2,&stu3; / /声明基类指针数组声明基类指针数组for(int i=0;i3;i+)for(int i=0;iprint(); stui-print(); / /* *printprint已申明为虚函数已申明为虚函数* */ / /stu0 /stu0将调用将调用bachelorbachelor的的printprint /stu1 /stu1将调用将

58、调用mastermaster的的printprint /stu0 /stu0将调用将调用doctordoctor的的printprint 多 态 的 分 类 C+中多态的分类 重载多态:函数重载和运算符重载 强制多态:强制类型转换 包含多态:虚函数 参数多态:函数模板和类模板 多 态 的 编 译 实 现 编译系统要根据已有的信息,对于同名函数的调用做出判断,确定调用搞得是哪个函数 关联/binding/联编/绑定 静态多态 静态关联、早期关联 动态多态 动态关联、滞后关联 C+规定,动态关联通过继承和虚函数来实现虚 函 数 申 明 注 意 事 项 静态成员函数不能声明为虚函数。因为静态成员函数

59、不属于某一个对象,没有多态性的特征 构造函数不能是虚函数 内联成员函数不能声明为虚函数。因为内联函数的执行代码是明确的,在编译时已被替换,没有多态性的特征 析构函数可以是虚函数,且往往被定义为虚函数。一般来说,若某类中有虚函数,则其析构函数也应当定义为虚函数虚 析 构 函 数申明格式virtual ();必要性实例若析构函数并非虚函数 stu是一个指向基类的指针变量,指向new开辟的空间 new开辟的空间是按照派生类doctor开辟的,所以除了name、ID之外,还为major、advisor、score申请了空间 delete stu仅仅调用了基类student的析构函数,仅仅释放name、

60、ID的空间,而没有释放major、advisor、score的空间student student * *stu = stu = new doctornew doctor; ; /声明基类指针指向声明基类指针指向doctordoctor类类stu-print(); stu-print(); / /* *printprint已申明为虚函数已申明为虚函数* */ / /stu /stu将调用将调用doctordoctor的的printprintdelet stu;delet stu;return 0;return 0;纯 虚 函 数 实例分析 福州大学中的学生总共就3类(bachelor、master、doctor),它们的共有基类student实际上是不需要实例化为任何对象的,因此student其实无需为虚函数print定义具体的实现 尽管基类本

温馨提示

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

评论

0/150

提交评论