windows界面程序开发_第3章 C++面向对象程序设计_第1页
windows界面程序开发_第3章 C++面向对象程序设计_第2页
windows界面程序开发_第3章 C++面向对象程序设计_第3页
windows界面程序开发_第3章 C++面向对象程序设计_第4页
windows界面程序开发_第3章 C++面向对象程序设计_第5页
已阅读5页,还剩122页未读 继续免费阅读

下载本文档

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

文档简介

1、1,第 3 章 C+面向对象程序设计,2,本章主要内容: 抽象性:类和对象 封装性:数据和方法 继承性:类的派生 多态性:虚函数,重载,模板,与传统的面向过程的程序设计语言相比,C+语言的最大特征是支持面向对象程序设计OOP(Object Oriented Programming),它引入了类、继承、多态和重载等面向对象的新机制。通过本章的学习,使我们系统地介绍C+面向对象设计的基本方法。,3,结构化程序设计的特点: 是一种自上而下、逐步细化的模块化程序设计方法。 Wirth N的观点:算法 + 数据结构 = 程序 是一种面向过程程序设计方法,即一个程序是由多个过程(在C+中为函数)模块组成,

2、过程之间通过函数参数和全局变量进行相互联系。,3.1 面向对象程序设计概述,3.1.1 结构化程序设计,(),4,与非结构化程序相比,结构化程序在调试、可读性和可维护性等方面都有很大的改进。 代码重用性不高:以过程为中心设计新系统,除了一些标准函数,大部分代码都必须重新编写。 由于软、硬件技术的不断发展和用户需求的变化,按照功能划分设计的系统模块容易发生变化,使得开发出来的模块的可维护性欠佳。 面向过程模式将数据与过程分离,若对某一数据结构做了修改,所有处理数据的过程都必须重新修订,这样就增加了很多的编程工作量。,结构化程序设计的特点:,5,什么是对象: 现实世界是由各种各样的事物组成,包括真

3、实的事物和抽象的事物。例如,人、动物、汽车(真实的事物)和程序、直线(抽象的事物)等。 每一类事物都有自己特定的属性(如大小、形状、重量等)和行为(如生长、行走、转弯、运算等),人们通过研究事物的属性和行为而认识事物。 在计算机科学中将这些现实世界中的事物称之为对象。对象是包含现实世界中事物特征的抽象实体,它反映了系统为之保存信息和与之交互的方法。 在程序设计领域,可以用如下公式表示: 对象 = 数据 + 作用于这些数据上的操作,3.1.2 面向对象程序设计方法及特征,(),6,为了描述属性和行为相同的一类对象,引入了类(class)的概念。 类是具有相同数据结构(属性)和相同操作功能(行为)

4、的对象的集合,它规定了这些对象的公共属性和行为方法。 对象是类的一个实例,例如,汽车是一个类,而行驶在公路上的一辆汽车则是一个对象。 对象和类的关系相当于程序设计语言中变量和变量类型的关系。,什么是类:,(),7,OOP围绕现实世界的概念来组织模块,采用对象描述问题空间的实体,用程序代码模拟现实世界中的对象,使程序设计过程更自然、更直观。 SP(结构化程序设计)是以功能为中心来描述系统,而OOP(面向对象程序设计)是以数据为中心来描述系统。相对于功能而言,数据具有更强的稳定性。 OOP模拟了对象之间的通信。就象人们之间互通信息一样,对象之间也可以通过消息进行通信。这样,我们不必知道一个对象是怎

5、样实现其行为的,只需通过对象提供的接口进行通信并使用对象所具有的行为功能。,面向对象程序设计的特点:,8,OOP把一个复杂的问题分解成多个能够完成独立功能的对象(类),然后把这些对象组合起来去完成这个复杂的问题。 一个对象可由多个更小的对象组成,如汽车由发动机、传送系统和排气系统等组成。这些对象(类)可由不同的程序员来设计,可在不同程序中使用,就象一个汽车制造商使用许多零部件去组装一辆汽车,而这些零部件可能不是自己生产的。 采用面向对象模式就象在流水线上工作,我们最终只需将多个零部件(已设计好的对象)按照一定关系组合成一个完整的系统。,面向对象程序设计的特点:,9,class Time pri

6、vate: int hour;/ 数据成员,表示小时 int minute;/ 数据成员,表示分钟 int second;/ 数据成员,表示秒 public: void setTime(int h, int m, int s) / 成员函数,设置时间 hour = (h=0 ,一个简单例子:,(),10,main() Time EndTime;/ 声明对象EndTime / 设置对象EndTime的时间(属性,数据成员) EndTime.setTime(12, 23, 36); coutThe time is:; EndTime.showTime(); / 显示对象EndTime的时间 ,运行

7、结果: The time is:12 : 23 : 36,11,面向对象程序设计方法的基本特征,面向对象程序设计方法具有四个基本特征: 抽象 封装 继承 多态性,1. 抽象 抽象是人类认识问题的最基本手段之一。抽象是指对具体问题(对象)进行概括,抽出一类对象的公共属性和行为并加以描述的过程。抽象包括数据抽象和代码抽象(或行为抽象)。,12,2. 封装 封装是把每个对象的数据(属性)和操作(行为)包装在一个类中。一旦定义了对象的属性和行为,则必须决定哪些属性和行为只用于表示内部状态,哪些属性和行为在外部是可见的。 一般限制直接访问对象的属性,而应通过操作接口访问,这样使程序中模块之间关系更简单、

8、数据更安全。对程序的修改也仅限于类的内部,使得由于修改程序所带来的影响局部化。,13,3. 继承 继承是指一个新类可以从现有的类派生而来。新类继承了现有类的特性,包括一些属性和行为,并且可以修改或增加新的属性和行为,使之适合具体的需要。 例如,所有的Windows应用程序都有一个窗口,它们可以看作都是从一个窗口类派生出来的,但有的应用程序用于文字处理,有的应用程序用于绘图,这是由于派生出了不同的类,它们增加了不同的属性和行为。 继承很好地解决了软件的可重用性问题。,14,4. 多态性 多态性是指类中具有相似功能的不同函数使用同一个名称来实现,并允许不同类的对象对同一消息作出的响应不相同。 例如

9、,同样的“编辑|粘贴”操作,在字处理程序和绘图程序中有不同的结果;同样的加法,把两个时间值相加和把两个整数相加的要求肯定不同。 多态性使程序设计灵活、抽象,具有行为共享和代码共享的优点,很好地解决了程序的函数同名问题。,15,为了支持面向对象程序设计,C+在C语言结构(struct)数据类型的基础上引入了类这种抽象数据类型。 C+面向对象编程实质上就是面向类编程,只有定义和实现了类,才能声明属于这个类的对象,才能通过对象使用定义的成员。 传统C程序员把编程重点放在函数的编写上,而C+程序员把重点放在类的定义和实现上。,3.2 C+类,16,C+类将对象的属性抽象为数据成员,将对象的行为抽象为成

10、员函数,并对它们进行封装。数据成员又称成员变量,成员函数又称为方法。 C+类在形式上类似于C语言中用户自定义的结构类型,但定义类时规定了成员的访问控制权限。对象只能访问所属类的公有成员,而类的私有成员只能在类的成员函数中被访问。,3.2.1 类的定义与实现,17,class private: ; public: ; protected: ; ;,C+类定义的基本形式:,(),18,类的定义由关键字class开始,其后为用户定义的类名,花括号括起来的部分称为类体。 关键字private、public和protected称为访问权限控制符,用来设置数据成员和成员函数的访问属性,其默认值为priva

11、te。 private属性表示数据成员和成员函数是类的私有成员,它们只允许被本类的成员函数访问或调用,数据成员一般定义为private属性;,说明:,(),19,public属性表示数据成员和成员函数是类的公有成员,它们允许被本类或其它类的成员函数(通过对象)访问或调用,是类的外部接口,成员函数一般定义为public属性; protected属性表示数据成员和成员函数是类的保护成员,它们允许被本类的成员函数和派生类的成员函数访问或调用。,说明:,(),20,class Time private:/ 最好不要省略private int hour; / 数据成员,表示小时 int minute;/

12、 数据成员,表示分钟 int second;/ 数据成员,表示秒 public: void setTime(int, int, int);/ 成员函数,设置时间 void showTime( );/ 成员函数,输出时间 ;,例 定义类Time(表示时间),私有数据成员hour、minute 和second只能在类的成员 函数中被访问或赋值;,公有成员函数setTime、showTime 可在外部被调用,但必须通过一个 对象作为对象的成员使用。,(),21,利用C+类进行面向对象编程,定义类的成员只是完成了工作的第一步,最重要的工作是实现定义的类。 类的实现实质上是类的成员函数的实现,即定义类的

13、成员函数。 成员函数的定义形式与一般函数的定义形式基本相同,但必须在成员函数名前加上类名和作用域限定符(:)。 成员函数的定义也可放在类体内(该函数声明之处),这时成员函数将变成内联函数。,类的实现:,(),22,void Time:setTime(int h, int m, int s) hour=(h=0 ,例 类Time的实现。,private成员hour、minute和 second不允许外界存取, 所以为类Time增加两个 public成员函数,供外界 设置或显示private成员。,(),23,一般将类的定义放在头文件(.h)中,类的实现放在源文件(.cpp)中,而main主函数可

14、以放在另一个源文件中。在源文件中用#include编译预处理指令包含头文件。,C+面向对象编程约定之一:,对象是类的一个实例,定义并实现了类,就可以利用定义好的类来声明对象,即创建对象。声明对象的形式与声明普通变量类似,例如: Time t1, start ; Point *pt1 = / 声明对象EndTime EndTime.setTime(12, 23, 36); / 设置对象EndTime的时间 coutThe time is:; EndTime.showTime( ); / 显示对象EndTime的时间 ,例 类Time的使用,声明对象并设置对象属性。,(),26,在定义类时不能对成

15、员变量进行初始化,因为无法确定成员变量属于哪一个对象。 成员变量一般都定义为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化。 成员变量的初始化一般是利用一个名为构造函数的成员函数来完成。,3.2.2 构造函数和析构函数,27,构造函数是一种特殊的成员函数,它是在创建对象时(声明或new动态创建)系统自动调用的成员函数。,什么是构造函数:,析构函数也是一种特殊的成员函数,它是在对象生存期结束时系统自动调用的成员函数。,什么是析构函数:,构造函数的名称与类名相同,析构函数的名称必须在类名前加上“”符号。注意,构造函数和析构函数不能指定任何返回值类型,包括void返回类型。,(),28

16、,#include class Time private: int hour; int minute; int second; public: Time(int, int, int); / 构造函数 Time( ); / 析构函数 . . . . . . ;,例 为类Time添加构造函数和析构函数。,(),29,Time:Time( ) coutThe destructor be called: hour: minute:secondendl; ,Time:Time(int h, int m, int s) hour = h;/ 对私有成员变量初始化 minute = m; second =

17、s; coutThe constructor be called: hour: minute:secondendl; ,构造函数和析构函数的实现:,(),30,void main(void) Time t1(10, 35, 55) ; / 自动调用构造函数 Time t2(16, 53, 9) ; / 自动调用构造函数 / 退出main()主函数时自动调用析构函数,构造函数和析构函数的自动调用:,程序运行结果为: The constructor be called:10:35:55 The constructor be called:16:53:9 The destructor be call

18、ed:16:53:9 The destructor be called:10:35:55,(),31,3.2.3 this指针,this指针是一个特殊的隐藏在对象中的指针,每一个处于生存期的对象都有一个this指针,用于指向对象本身。 当类的某个非静态成员函数被调用时,系统通过this指针确定是哪一个对象的该成员函数被调用。实际上,this指针总是作为一个隐含参数传递给类的每一个成员函数。,32,下面定义的成员函数并没有声明this参数:,void Time:showTime( ) couthour:minute:secondendl; ,编译器会把this指针作为成员函数的参数:,void

19、Time:showTime(Time* this); couthourminutesecondendl; ,33,调用时:,当程序中调用某个成员函数时,编译器会把该对象的地址赋值给this指针,并将该地址值加入到参数表中,如下所示: EndTime.showTime(,作用:,在一个成员函数中经常需要调用其它函数(非本类的成员函数),而有时需要把对象本身(即对象的地址)作为参数传递给被调用函数,这时必须使用this指针。,34,例 this指针的使用。,#include #include class Person public: / 可在外部直接访问public属性的数据成员 char m_s

20、trName20; char m_ID18; public: Person(char* strName, char* ID) / 内联构造函数 strcpy(m_strName, strName); strcpy(m_ID, ID); void Show(); ;,35,void Display(Person* pObj)/ 非成员函数 coutm_strNamem_IDShow(); / 通过调用Show调用Display ,36,3.2.4 静态成员,静态成员的概念: 一般情况下,同一个类不同对象的数据成员所占用的内存空间是不同的(体现了不同对象具有不同的属性值)。在有些情况下,类的数据成

21、员的值对每个对象都是相同的,如当前已创建对象的数量,这时可以将该数据成员声明为静态数据成员(占有相同的存储单元)。,静态成员的声明: 在声明成员时以关键字static开头,例如: public: static int m_nCount;,37,说明:,静态成员分为静态数据成员和静态成员函数。 静态数据成员类似于一般的static静态变量,它具有全局性。静态数据成员属于整个类,为类的所有对象共享。 无论类的对象有多少,类的静态数据成员只有一份,存储在同一个内存空间。即使没有创建类的一个对象,类的静态数据成员也是存在的。 使用静态数据成员保证了该数据成员值的唯一性。,静态成员的初始化:放在类定义的

22、外部 int Person : m_nCount = 0;,38,静态成员的访问:,公有静态成员:三种方式 (1)通过对象访问,如: person1.m_nCount = 100; (2)利用类名和作用域限定符(:)访问,如: int Person:m_nCount = 100; / 初始化 (3)在成员函数中访问,如: m_nCount+;,私有和保护静态成员:只能在成员函数中访问,39,静态成员函数:,成员函数也可以是静态的,其声明方式与静态成员变量类似。如: public: static int GetCount();/ 获取静态数据成员 静态成员函数也与一个类相关联,而不只与一个特定的

23、对象相关联。 区别非静态成员函数,静态成员函数没有this指针,因为类的静态成员函数只有一个运行实例。 成员函数一般是公有属性,可以通过对象、类名和作用域限定符、在成员函数中三种方式调用静态成员函数。,40,静态成员函数只能访问类的静态成员(成员变量和成员函数),而不能访问类的非静态成员。因为当通过类名和运算符“:”调用一个静态成员函数时,不能确定函数中所访问的非静态成员属于哪一个对象。 解决方法: 将对象作为静态成员函数的参数,然后在静态成员函数中通过对象访问它的非静态成员。,41,例 静态成员变量和静态成员函数的使用。,#include #include class Person publ

24、ic: char m_strName20; long m_ID; static int m_nCount; / 静态成员变量,表示已创建对象的数量 public: Person(char*, long);/ 构造函数 static int GetCount();/ 静态成员函数 static long GetID(Person); / 对象作为静态成员函数的参数 ;,42,Person:Person(char* strName, long ID) strcpy(m_strName, strName); m_ID = ID; m_nCount+;/ 对象数目加1 int Person:GetCo

25、unt() return m_nCount;/ 访问静态成员变量 long Person:GetID(Person x) return x.m_ID; / 不能直接访问非静态成员m_ID ,43,int Person:m_nCount = 0;/ 初始化静态成员变量 void main( ) Person e1(LiuJun,1101051); coutPerson:m_nCount , e1.m_nCount n; / 通过类或对象访问静态成员变量 coutPerson:GetCount() , ” Person:GetID(e1)n; / 通过类调用静态成员函数 coute1.GetCou

26、nt() , e1.GetID(e1)n; / 通过对象调用静态成员函数 Person e2(WangXiaogang,1101058); coutPerson:GetCount() , ” Person:GetID(e2)n; coute2.GetCount() , e2.GetID(e2)n; coute1.GetCount() , e1.GetID(e1)n; / e1和e2共享静态成员变量m_nCount ,44,3.2.5 友元,类具有封装性,类的私有成员一般只能通过该类的成员函数访问,这种封装性隐藏了对象的数据成员,保证了对象的安全,但有时带来了编程的不方便。,友元函数: C+提供

27、了一种函数,它虽然不是一个类的成员函数,但可以象成员函数一样访问该类的所有成员,包括私有成员和保护成员。这种函数称为友元(friend)函数。,45,一个函数要成为一个类的友员函数,需要在类的定义中声明该函数,并在函数声明的前面加上关键字friend。 友元函数本身的定义没有什么特殊要求,可以是一般函数,也可以是另一个类的成员函数。 为了能够在友元函数中访问并设置类的私有数据成员,一个类的友元函数一般将该类的引用作为函数参数。,友元函数的声明:,46,例如:,class A friend void display(A); / 友元函数是一个一般函数 friend void B:BMemberF

28、un(A / 友元函数是另一个类B的成员函数 public: . . . ,47,友元类: 友元的另一种类型是友元类,一个类可以声明另一个类为其友元类,这个友元类的所有成员函数都可以访问声明其为友元的类的所有成员。 由于访问权限控制符不影响友元声明,友元声明可放在类体中任何地方,建议把友元声明放在类体的开始位置。,48,3.3 类的继承,继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。 在C+面向对象程序设计中,所谓类的继承就是利用现有的类创建一个新的类。新类继承了现有类的属性和行为。 为了使新类具有自己所需的功能,它可以扩充和完善现有类的属性和行为,使之更具体。

29、微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性。,49,3.3.1 基类和派生类,在现实世界中,一类事物的对象常常也属于另一类事物。 在面向对象程序设计方法中,一个类的对象也常常是另一个类的对象,即一个类具有了另一个类的属性和方法。 在定义一个类时,根据类的继承性,我们能够且应尽可能地利用现有的类来定制新的类,而不必重新设计新的类。,1. 问题的提出,(),50,在继承关系中,新定义的类称为被继承类的派生类或子类,而被继承的类称为新定义类的基类或父类。派生类继承了基类的所有成员。 一个派生类也可以作为另一个派生类的基类。,2. 基类和派生类的概念,class : . . . /

30、派生类新增加的成员声明列表 ;,3. 派生类的定义,(),51,派生方式决定了基类的成员在派生类中的访问权限。派生方式共有三种:public、private和protected(缺省值为private)。 虽然派生类继承了基类的所有成员,但为了不破坏基类的封装性,无论采用哪种派生方式,基类的私有成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。,说明:,(),52,采用public派生,基类成员的访问权限在派生类中保持不变,即基类所有的公有或保护成员在派生类中仍为公有或保护成员。public派生最常用。 (1) 可以在派生类的成员函数中访问基类的非私有成员; (2)

31、可通过派生类的对象直接访问基类的公有成员。 采用private私有派生,基类所有的公有和保护成员在派生类中都成为私有成员,只允许在派生类的成员函数中访问基类的非私有成员。private派生很少使用。 采用protected保护派生,基类所有的公有和保护成员在派生类中都成为保护成员,只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员。,三种派生方式的区别:,(),53,例 定义类Point,然后定义类Point的派生类Circle。,#include class Point/ 定义基类,表示点 private: int x; int y; public: void set

32、Point(int a, int b) x = a; y = b; ; / 设置坐标 int getX( ) return x; ;/ 取得X坐标 int getY( ) return y; ;/ 取得Y坐标 ;,(),54,class Circle : public Point / 定义派生类,表示圆 private: int radius; public: void setRadius(int r) radius = r; ; / 设置半径 int getRadius( ) return radius; ; / 取得半径 int getUpperLeftX( ) return getX()

33、 - radius; ; / 取得外接正方形左上角的X坐标 int getUpperLeftY( ) return getY() + radius; ; / 取得外接正方形左上角的Y坐标 ;,(),55,main( ) Circle c; c.setPoint(200, 250); c.setRadius(100); coutX=c.getX( ), Y=c.getY( ) , Radius=c.getRadius()endl; coutUpperLeft X=c.getUpperLeftX( ) , UpperLeft Y=c.getUpperLeftY( )endl; ,公有派生类的对象可

34、以直接访问基类Point的公有成员,程序运行结果:X=200,Y=250,Radius=100UpperLeft X=100,UpperLeft Y=350,(),56,派生类Circle通过public派生方式继承了基类Point的所有成员(除私有成员外所有成员的访问权限不变),同时还定义了自己的成员变量和成员函数。 若将类Circle的派生方式改为private或protected,则下述语句是非法的:c.setPoint(200, 250); 无论哪种派生方式,派生类都继承了基类的所有成员,包括私有成员。我们虽然不能在派生类Circle中直接访问私有数据成员x和y,但可以通过继承的公有成

35、员函数getX( )、getY( )和setPoint( )访问或设置它们。,说明:,(),57,利用类继承定义类可能带来一个问题:派生类会继承它不需要的基类中的数据成员和成员函数,这时,基类中不适合于派生类的成员可以在派生类中重新加以定义。,例 派生类成员函数对基类成员函数的覆盖。,#include class A public: void Show( ) coutA:Shown; ; ;,58,class B : public A public: void Show( ) coutB:Shown; ; / 在派生类中重新定义成员函数 void Display( ) Show( ); ; /

36、 调用派生类B的成员函数Show() ; void main( ) A a; B b; a.Show( );/ 调用基类A的成员函数Show() b.Show( );/ 调用派生类B的成员函数Show() b.Display( ); ,如果想调用基类A的成员函数Show(),可以使用作用域限定符“:”:A: Show();,59,为什么我们经常在现有类的基础上采用继承的方法来定制新类,而不通过直接修改现有类来设计自己的类?除了代码重用的优越性,其主要原因是可能得不到基类的实现源码。,继承的 重要性!,在利用微软基础类MFC派生自己的类时,我们只需要MFC类声明的头文件(利用#include指令

37、将头文件包含)和含有成员函数目标代码的OBJ文件,并不需要整个MFC类库的实现源码。,60,3.3.2 基类和派生类的构造函数,一个派生类对象也属于其基类,因此当程序创建一个派生类对象时,系统首先自动创建一个基类对象。 在调用派生类的构造函数构建派生类对象时,系统首先调用基类的构造函数构建基类对象。当派生类对象的生存期结束时,首先调用派生类的析构函数,然后调用基类的析构函数。,1. 问题的提出,编译器在对程序编译时,首先生成基类构造函数的调用代码,然后生成派生类构造函数的调用代码。,(),61,隐式调用和显式调用两种方式:,2. 基类构造函数的调用方式,注意:除非基类有默认的构造函数,否则必须

38、采用显式调用方式。,(1)隐式方式是指在派生类的构造函数中不指定对应的基类的构造函数,调用的是基类的默认构造函数(即含有缺省参数值或不带参数的构造函数)。 (2)显式方式是指在派生类的构造函数中指定要调用的基类构造函数,并将派生类构造函数的部分参数值传递给基类构造函数。,62,设类B是类A的派生类,则派生类B显式方式构造函数的定义形式如下:,3. 显式方式构造函数的定义,B:B( ) : A( ) . . . / 类B构造函数的实现代码 ,形参声明中的部分参数,传递给基类构造函数,派生类构造函数形参的名称和类型,派生类构造函数既初始化派生类的数据成员,又通过基类构造函数初始化其基类的数据成员。

39、 参数表中参数的个数和类型要与基类某个构造函数的形参声明一致。,PointCircle Cylinder,注意: 当基类有多个构造函数时,编译器根据派生类构造函数为基类构造函数提供的参数表来确定调用基类的哪一个构造函数。,例 首先定义类Point,然后定义类Point的派生类Circle,再定义类Circle的派生类Cylinder。,(),64,#include class Point/ 定义基类Point protected: int x, y; public: Point(int a=0, int b=0) / 含有缺省参数值的构造函数也是默认的构造函数 x = a; y = b; co

40、utPoint constructor:x,yendl; ; Point( ) coutPoint destructor:x,yendl; ; ;,(),65,class Circle : public Point/ 定义类Point的派生类 protected: int radius; public:/ 显式调用基类的构造函数 Circle(int a=0, int b=0, int r=0) : Point(a, b) radius = r; coutCircle constructor:radius x,yendl; ; Circle( ) coutCircle destructor:r

41、adiusx,yendl; ; ;,(),66,class Cylinder : public Circle/ 定义类Circle的派生类 protected: int height; public: / 显式调用基类的构造函数 Cylinder(int a=0, int b=0, int r=0, int h=0) : Circle(a, b, r) height=h; coutCylinder constructor:height radiusx,yendl; ; Cylinder( ) coutCylinder destructor:height radiusx,yendl; ; ;,(

42、),67,main( ) Cylinder cylinder(200, 300, 100, 400); / 调用了类Point、Circle和Cylinder的构造函数 ,Point constructor:200,300 Circle constructor:100 200,300 Cylinder constructor:400 100 200,300 Cylinder destructor:400 100 200,300 Circle destructor:100 200,300 Point destructor:200,300,程序运行结果:,(),68,构造函数的执行顺序:,析构函数

43、的执行顺序:,Point( ),Circle( ),Cylinder( ),当声明Cylinder对象时, Cylinder( ), Circle( ), Point( ),当程序结束时,(),69,3.3.3 多重继承,1. 单继承和多重继承的概念,class A,class B,class C,class A,class B,class C,每个派生类只有一个直接基类 单继承,一个派生类同时从多个基类派生而来,即有多个直接基类 多重继承,(),70,2. 多重继承派生类的定义,设类B是类A1、A2、An的派生类,多重继承的派生类的定义形式为:,class : , , , . . . / 派

44、生类新增加的成员声明列表 ;,多重继承的派生方式也有private、public和protected三种,各基类的派生方式可以不同,71,3.3.4 虚基类,1. 多重继承中的 二义性问题,class C : public A public: int c; ; class D : public B, public C / 类D派生于类B和类C public: int d; ; main( ) D d1; d1.a = 100; ,class A public: int a; ; class B : public A public: int b; ;,二义性错误: 编译器无法确定数据成员a是哪一

45、个副本,72,class B,class C,class D,class A,派生类D的对象中存在间接基类A的两份副本,(),73,2. 解决方法,利用作用域限定符(:)把基类的成员与下一层基类关联起来: d1.B:a=100; 或: d1.C:a=100,从路径DBA继承而来,从路径DCA继承而来,缺点: 浪费了存储空间; 在访问基类的成员时,要求指明访问路径。 大部分情况下不需要保存基类多个相同的副本。,74,3. 使用虚基类,虚基类并不是一种新的类型的类,而是一种派生方式。采用虚基类方式定义派生类,在创建派生类的对象时,类层次结构中虚基类的成员只出现一次,即基类的一个副本被所有派生类对象

46、所共享。,75,虚基类派生方式的定义:,例 采用virtual虚基类方式定义派生类。,class B : virtual public A public: int b; ;,class C : virtual public A public: int c; ;,主函数中: d1.a=100;,采用虚基类方式定义派生类的方法是在基类的前面加上关键字virtual,而定义基类时与一般基类完全一样。,76,3.4 多态性和虚函数,何谓多态性?,多态性也是面向对象程序设计方法的一个重要特征,它主要表现在函数调用时实现“一种接口、多种方法”。 两种多态性:编译时多态性和运行时多态性。 编译时多态性:在函

47、数名或运算符相同的情况下,编译器在编译阶段就能够根据函数参数类型的不同来确定要调用的函数 通过重载实现。 运行时多态性:在函数名、函数参数和返回类型都相同的情况下,只能在程序运行时才能确定要调用的函数 通过虚函数实现。,(),77,3.4.1 用基类指针指向派生类对象,声明一个派生类的对象的同时也自动声明了一个基类的对象。 派生类的对象可以认为是其基类的对象。C+允许一个基类对象的指针指向其派生类的对象 这是实现虚函数的关键 不允许派生类对象的指针指向其基类的对象。 即使将一个基类对象的指针指向其派生类的对象,通过该指针也只能访问派生类中从基类继承的公有成员,不能访问派生类自定义的成员,除非通

48、过强制类型转换将基类指针转换为派生类指针。,78,例 基类指针与派生类指针之间的相互转换。,class A private: int a; public: void setA(int i) a = i; ; void showA( ) couta=an; ; ;,class B : public A private: int b; public: void setB(int i) b= i; ; void showB( ) coutb=bn; ; ;,79,void main( ) A a, *pa;/ pa为基类对象的指针 B b, *pb;/ pb为派生类对象的指针 pa = ,程序运行结

49、果为: a=100 b=200,pb= ; ;,void main( ) A *pa; B b; pa = ,class B : public A public: void Show( ) coutB:Shown; ; ;,如果想通过基类指针调用派生类中覆盖的成员函数,只有使用虚函数。,81,要将一个成员函数声明为虚函数,只需在定义基类时在成员函数声明的开始位置加上关键字virtual。,2. 虚函数的声明,class A public: virtual void Show( ) coutA:shown; ; ; class B : public A public: void Show( )

50、coutB:shown; ; ;,82,void main() A a, *pa; B b; pa = / 调用函数B:Show( ),总结:利用虚函数可以在基类和派生类中使用相同的函数名和参数类型,但定义不同的操作。这样,就为同一个类体系中所有派生类的同一类行为(其实现方法可以不同)提供了一个统一的接口。 例如,在一个图形类继承结构中,设类CShape是所有具体图形类(如矩形、三角形或圆等)的基类,则函数调用语句“pShape-Draw()”可能是绘制矩形,也可能是绘制三角形或圆。具体绘制什么图形,取决于pShape所指的对象。,83,当撤消派生类的对象时,先调用派生类析构函数,然后自动调用

51、基类析构函数,如此看来析构函数没必要定义为虚函数。但是,假如使用基类指针指向其派生类的对象,而这个派生类对象是用new运算创建的。当程序使用delete运算撤消派生类对象时,这时只调用了基类的析构函数,而没有调用派生类的析构函数。 如果使用虚析构函数,无论指针所指的对象是基类对象还是派生类对象,程序执行时都会调用对应的析构函数。,4. 构造函数、析构函数与虚函数,基于构造函数的特点,不能将构造函数定义为虚函数。,84,例 虚析构函数的使用。,class A public: A( ) ;/ 构造函数不能是虚函数 virtual A( ) coutA:destructorn; ; / 析构函数是虚

52、函数 ; class B : public A public: B( ) ; B( ) coutB:destructorn; ; / 虚析构函数 ;,85,void main() A *pA = new B; / . . . . . . delete pA; ,程序运行结果: B:destructor A:destructor,总结:由于使用了虚析构函数,当撤消pA所指派生类B的对象时,首先调用派生类B的析构函数,然后再调用基类A的析构函数。,86,抽象类是类的一些行为(成员函数)没有给出具体定义的类,即纯粹的一种抽象。 抽象类只能用于类的继承,其本身不能用来创建对象,抽象类又称为抽象基类。

53、抽象基类只提供了一个框架,仅仅起着一个统一接口的作用,而很多具体的功能由派生出来的类去实现。 虽然不能声明抽象类的对象,但可以声明指向抽象类的指针。,3.4.3 抽象类和纯虚函数,1. 何谓抽象类,在一般的类库中都使用了抽象基类,如类CObject就是微软基础类库MFC的抽象基类。,87,2. 抽象类的定义,不定义具体实现的成员函数称为纯虚函数。纯虚函数不能被调用,仅起提供一个统一接口的作用。 纯虚函数的声明: virtual ()= 0 ; 当基类是抽象类时,只有在派生类中重新定义基类中的所有纯虚函数,该派生类才不会再成为抽象类。,一个类如果满足以下两个条件之一就是抽象类: 至少有一个成员函

54、数不定义具体的实现; 定义了一个protected属性的构造函数或析构函数。,3. 纯虚函数,88,例 纯虚函数和抽象类。,/ 定义抽象基类 class CShape public: double r, s ; public: CShape(double x) r = x; / 声明纯虚函数 virtual void Area( ) = 0; ;,/ 定义具体的派生类 class CCircle : public CShape public: CCircle(double x):CShape(x) ; / 重新定义虚函数 void Area( ) s = 3.14159*r*r; ;,main

55、( ) CCircle circle(48.52); circle.Area( ); coutArea=circle.sendl; ,89,3.5 重载,重载是C+提供的一个新特性。C+重载分为函数重载和运算符重载,这两种重载的实质是一样的,因为进行运算可以理解为是调用一个函数。,通过使用重载机制,可以对一个函数名(或运算符)定义多个函数(或运算功能),只不过要求这些函数的参数(或参加运算的操作数)的类型有所不同。 重载使C+程序具有更好的可扩充性。,x + y X + Y,Add(x, y) Add(x, y, z),(),90,函数重载:指一组功能类似但函数参数类型(或个数)不同的函数可以

56、共用一个函数名。 当C+编译器遇到重载函数的调用语句时,它能够根据不同的参数类型或不同的参数个数选择一个合适的函数。,3.5.1 函数重载,int abs(int val) return val0 ? val : val; ,float abs(float val) return (val0) ? val : val; ,例 通过函数参数类型的不同实现函数重载。,(),91,main( ) int i = 100; coutabs(i)endl; / int型 float f = -125.78f; coutabs(f)endl; / float型 ,在程序中,求绝对值函数的名称相同,但参数类

57、型不同,这时C+编译器自动按参数表的不同来分别联编不同的求绝对值函数。,不能利用函数返回类型的不同进行函数重载。因为在没有确定调用的是哪个函数之前,不知道函数的返回类型。,long abc(int); float abc(int);,(),92,同样,不能利用引用进行函数重载: void fun(int/ i是一个整型变量,从上面可以看出,一般函数的重载使C+程序具有更好的可扩充性。此外,类的成员函数也可以重载,特别是构造函数的重载给C+程序设计带来很大的灵活性。,93,例 构造函数的重载。,class Box private: int height, width, depth; public: Box( ) height = 0; width = 0; depth = 0; / 避免给成员变量赋不安全的值 Box(int ht, int wd, int dp) / 重载构造函数 height = ht;

温馨提示

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

评论

0/150

提交评论