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

下载本文档

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

文档简介

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

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

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

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

5、对象所具有的行为功能。lOOP把一个复杂的问题分解成多个能够完成独立功能的对象(类),然后把这些对象组合起来去完成这个复杂的问题。l一个对象可由多个更小的对象组成,如汽车由发动机、传送系统和排气系统等组成。这些对象(类)可由不同的程序员来设计,可在不同程序中使用,就象一个汽车制造商使用许多零部件去组装一辆汽车,而这些零部件可能不是自己生产的。l采用面向对象模式就象在流水线上工作,我们最终只需将多个零部件(已设计好的对象)按照一定关系组合成一个完整的系统。class Time private: int hour;/ 数据成员,表示小时 int minute;/ 数据成员,表示分钟 int sec

6、ond;/ 数据成员,表示秒public: void setTime(int h, int m, int s) / 成员函数,设置时间 hour=(h=0 & h=0 & m=0 & s60) ? s:0; void showTime()/ 成员函数,输出时间 couthour:minute:secondendl; ;main() Time EndTime;/ 声明对象EndTime / 设置对象EndTime的时间(属性,数据成员) EndTime.setTime(12, 23, 36); coutThe time is:; / 显示对象EndTime的时间 EndTime.showTime

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

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

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

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

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

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

13、,但必须在成员函数名前加上类名和作用域限定符(:)。l成员函数的定义也可放在类体内(该函数声明之处),这时成员函数将变成内联函数。 例:例:类的实现:类的实现:void Time:setTime(int h, int m, int s) hour=(h=0 & h=0 & m=0 & s60) ? s:0;void Time:showTime() couthour:minute:second”访问对象的公有成员,但不能访问对象的私有成员。 例如,公有成员函数调用:t1.setTime();start.showTime();pt1-setTime(); 而任何形如t1.hour、t1.minut

14、e、start.second等私有成员变量的直接访问都是非法的。成员的访问:成员的访问: 例:例:main() Time EndTime;/ 声明对象EndTime EndTime.setTime(12, 23, 36);/ 设置对象EndTime的时间 coutThe time is:; EndTime.showTime();/ 显示对象EndTime的时间l在定义类时不能对成员变量进行初始化,因为无法确定成员变量属于哪一个对象。l成员变量一般都定义为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化。l成员变量的初始化一般是利用一个名为构造函数构造函数的成员函数来完成。 3.2.

15、2 构造函数和析构函数构造函数和析构函数如何进行成员变量的初始化? 构造函数是一种特殊的成员函数,它是在创建对象时(声明或new动态创建)系统自动调用的成员函数。什么是构造函数:什么是构造函数: 析构函数也是一种特殊的成员函数,它是在对象生存期结束时系统自动调用的成员函数。什么是析构函数:什么是析构函数: 构造函数的名称与类名相同,析构函数的名称必须在类名前加上“”符号。注意,构造函数和析构函数不能指定任何返回值类型,包括void返回类型。#include class Timeprivate: int hour; int minute; int second;public: Time(int,

16、 int, int);/ 构造函数 Time();/ 析构函数 . . . . . . ; 例例 为类Time添加构造函数和析构函数。Time:Time(int h, int m, int s) hour=h;/ 对私有成员变量初始化 minute=m; second=s; coutThe constructor be called: hour: minute:secondendl; 构造函数和析构函数的实现:构造函数和析构函数的实现:功能与成员函数Time:setTime()类似Time:Time() coutThe destructor be called: hour: minute:se

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

18、r be called:10:35:55为什么是这个结果? 当创建一个对象时,系统先根据类定义的成员变量为对象分配内存空间,然后自动调用对象的构造函数对这段内存空间进行初始化处理,从而完成对象的初始化。 当撤消一个对象时,系统先自动调用对象的析构函数,然后释放对象所占内存空间。 从程序的运行结果可以看出,析构函数的调用顺序一般与构造函数的调用顺序相反。 栈栈:后进先出表结果分析:结果分析:l与一般数据类型的变量相比,对象在它的生存期会有大量的操作,有时这些操作的结果必须在对象的生存期结束时加以清理。因此可以在析构函数中进行动态分配的内存清理工作。l如果定义类时没有提供构造函数和析构函数,编译系

19、统将会自动为类分别添加一个缺省的构造函数和析构函数。如果用户加上自定义的构造函数和析构函数,编译系统将不会再添加缺省的构造函数和析构函数。l若构造函数无参数,则声明对象时也不能给出参数。补充说明:补充说明: 3.2.3 this指针指针lthis指针是一个特殊的隐藏在对象中的指针,每一个处于生存期的对象都有一个this指针,用于指向对象本身。l当类的某个非静态成员函数被调用时,系统通过this指针确定是哪一个对象的该成员函数被调用。实际上,this指针总是作为一个隐含参数传递给类的每一个成员函数。 例:例:下面定义的成员函数并没有声明this参数:void Time:showTime() co

20、uthour:minute:secondendl;编译器会把this指针作为成员函数的参数:void Time:showTime(Time* * this); couthour:minute: secondendl;调用时:调用时: 当程序中调用某个成员函数时,编译器会把该对象的地址赋值给this指针,并将该地址值加入到参数表中,如下所示:EndTime.showTime(&EndTime);作用:作用: 在一个成员函数中经常需要调用其它函数(非本类的成员函数),而有时需要把对象本身(即对象的地址)作为参数传递给被调用函数,这时必须使用this指针。 例:例:例例 this指针的使用。#inc

21、lude #include class Personpublic: / 可在外部直接访问public属性的数据成员 char m_strName20; char m_ID18;public: Person(char* strName, char* ID) / 内联构造函数 strcpy(m_strName, strName); strcpy(m_ID, ID); void Show();void Display(Person* pObj)/ 非成员函数 coutName:m_strNameendlID:m_IDShow(); / 通过调用Show调用Display3.2.4 静态成员的概念:静

22、态成员的概念: 一般情况下,同一个类不同对象的数据成员所占用的内存空间是不同的(体现了不同对象具有不同的属性值)。在有些情况下,类的数据成员的值对每个对象都是相同的,如当前已创建对象的数量,这时可以将该数据成员声明为静态数据成员(占有相同的存储单元)。静态成员的声明:静态成员的声明: 在声明成员时以关键字static开头,例如: public: static int m_nCount;说明:说明:l静态成员分为静态数据成员和静态成员函数。l静态数据成员类似于一般的static静态变量,它具有全局性。静态数据成员属于整个类,为类的所有对象共享。l无论类的对象有多少,类的静态数据成员只有一份,存储

23、在同一个内存空间。即使没有创建类的一个对象,类的静态数据成员也是存在的。l使用静态数据成员保证了该数据成员值的唯一性。静态成员的初始化:静态成员的初始化:放在类定义的外部 int Person : m_nCount=0; l公有静态成员:三种方式(1)通过对象访问,如: person1.m_nCount=100;(2)利用类名和作用域限定符(:)访问,如:int Person:m_nCount=100; / 初始化(3)在成员函数中访问,如:m_nCount+;l私有和保护静态成员:只能在成员函数中访问l成员函数也可以是静态的,其声明方式与静态成员变量类似。如:public: static i

24、nt GetCount();/ 获取静态数据成员l静态成员函数也与一个类相关联,而不只与一个特定的对象相关联。l区别非静态成员函数,静态成员函数没有this指针,因为类的静态成员函数只有一个运行实例。l成员函数一般是公有属性,可以通过对象、类名和作用域限定符、在成员函数中三种方式调用静态成员函数。 静态成员函数只能访问类的静态成员(成员变量和成员函数),而不能访问类的非静态成员。因为当通过类名和运算符“:”调用一个静态成员函数时,不能确定函数中所访问的非静态成员属于哪一个对象。 解决方法:解决方法: 将对象作为静态成员函数的参数,然后在静态成员函数中通过对象访问它的非静态成员。注注 意意例例例

25、例 静态成员变量和静态成员函数的使用。#include #include class Personpublic: char m_strName20; long m_ID; static int m_nCount; / 静态成员变量,表示已创建对象的数量public: Person(char*, long);/ 构造函数 static int GetCount();/ 静态成员函数 static long GetID(Person);/ 对象作为静态成员函数的参数;Person:Person(char* strName, long ID) strcpy(m_strName, strName);

26、m_ID=ID; m_nCount+;/ 对象数目加1int Person:GetCount() return m_nCount;/ 访问静态成员变量long Person:GetID(Person x) return x.m_ID; / 不能直接访问非静态成员m_IDint Person:m_nCount=0;/ 初始化静态成员变量void main() Person e1(LiuJun,1101051); coutPerson:m_nCount , e1.m_nCount n; / 通过类或对象访问静态成员变量 coutPerson:GetCount() , ” Person:GetID(

27、e1)n; / 通过类调用静态成员函数 coute1.GetCount() , 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程序运行结果为:程序运行结果为: 1,11,11010511,11010512,11010582,11010582,1

28、101051作业:作业:P109-114,3-37(1),3-423.2.5 类具有封装性,类的私有成员一般只能通过该类的成员函数访问,这种封装性隐藏了对象的数据成员,保证了对象的安全,但有时带来了编程的不方便。友元函数:友元函数: C+提供了一种函数,它虽然不是一个类的成员函数,但可以象成员函数一样访问该类的所有成员,包括私有成员和保护成员。这种函数称为友元(friend)函数。 一个函数要成为一个类的友员函数,需要在类的定义中声明该函数,并在函数声明的前面加上关键字friend。 友元函数本身的定义没有什么特殊要求,可以是一般函数,也可以是另一个类的成员函数。 为了能够在友元函数中访问并设

29、置类的私有数据成员,一个类的友元函数一般将该类的引用作为函数参数。 例例class A friend void display(A);/ 友元函数是一个一般函数 friend void B:BMemberFun(A&); / 友元函数是另一个类B的成员函数public:. . . 友元类:友元类: 友元的另一种类型是友元类,一个类可以声明另一个类为其友元类,这个友元类的所有成员函数都可以访问声明其为友元的类的所有成员。 由于访问权限控制符不影响友元声明,友元声明可放在类体中任何地方,建议把友元声明放在类体的开始位置。例例 友元(一般友元函数、友元成员函数和友元类)的声明和使用。 P78-79,

30、例3-7。说明:说明: l友元关系是单方向的,不具有交换性和传递性。l使用友元虽然简化了编程,并可避免调用成员函数的开销,但破坏了类的封装性,建议谨慎使用。作业:作业:P114,3-43l继承继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。l在C+面向对象程序设计中,所谓类的继承就是利用现有的类创建一个新的类。新类继承了现有类的属性和行为。l为了使新类具有自己所需的功能,它可以扩充和完善现有类的属性和行为,使之更具体。l微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性。 继承继承发扬发扬l在现实世界中,一类事物的对象常常也属于另一类事物。l在面向对象程序

31、设计方法中,一个类的对象也常常是另一个类的对象,即一个类具有了另一个类的属性和方法。l在定义一个类时,根据类的继承性,我们能够且应尽可能地利用现有的类来定制新的类,而不必重新设计新的类。 在继承关系中,新定义的类称为被继承类的派派生类生类或子类子类,而被继承的类称为新定义类的基类基类或父类父类。派生类继承了基类的所有成员。 一个派生类也可以作为另一个派生类的基类。class : . . . / 派生类新增加的成员声明列表;l派生方式决定了基类的成员在派生类中的访问权限。派生方式共有三种:public、private和protected(缺省值为private)。l虽然派生类继承了基类的所有成员

32、,但为了不破坏基类的封装性,无论采用哪种派生方式,基类的私有成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。l采用public派生,基类成员的访问权限在派生类中保持不变,即基类所有的公有或保护成员在派生类中仍为公有或保护成员。public派生最常用。 (1) 可以在派生类的成员函数中访问基类的非私有成员; (2) 可通过派生类的对象直接访问基类的公有成员。l采用private私有派生,基类所有的公有和保护成员在派生类中都成为私有成员,只允许在派生类的成员函数中访问基类的非私有成员。private派生很少使用。l采用protected保护派生,基类所有的公有和保护成员

33、在派生类中都成为保护成员,只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员。 例 定义类Point,然后定义类Point的派生类Circle。 #include class Point/ 定义基类,表示点private: int x; int y;public: void setPoint(int a, int b) x=a; y=b; ; / 设置坐标 int getX() return x; ;/ 取得X坐标 int getY() return y; ;/ 取得Y坐标;class Circle : public Point / 定义派生类,表示圆private:

34、int radius;public: void setRadius(int r) radius=r; ; / 设置半径 int getRadius() return radius; ; / 取得半径 int getUpperLeftX() return getX()radius; ;/ 取得外接正方形左上角的X坐标 int getUpperLeftY() return getY() + radius; ;/ 取得外接正方形左上角的Y坐标;main() Circle c; c.setPoint(200, 250); c.setRadius(100); coutX=c.getX(), Y=c.ge

35、tY() , Radius=c.getRadius()endl; coutUpperLeft X=c.getUpperLeftX() , UpperLeft Y=c.getUpperLeftY()endl; 公有派生类的对象可以直接访问基类Point的公有成员l派生类Circle通过public派生方式继承了基类Point的所有成员(除私有成员外所有成员的访问权限不变),同时还定义了自己的成员变量和成员函数。l若将类Circle的派生方式改为private或protected,则下述语句是非法的:c.setPoint(200, 250); 容易混淆l无论哪种派生方式,派生类都继承了基类的所有成

36、员,包括私有成员。我们虽然不能在派生类Circle中直接访问私有数据成员x和y,但可以通过继承的公有成员函数getX()、getY()和setPoint()访问或设置它们。 利用类继承定义类可能带来一个问题问题:派生类会继承它不需要的基类中的数据成员和成员函数,这时,基类中不适合于派生类的成员可以在派生类中重新加以定义。 #include class Apublic:void Show( ) coutA:Shown; ;class B : public Apublic: void Show( ) coutB:Shown; ;/ 在派生类中重新定义成员函数 void Display() Show

37、( ); ;/ 调用派生类B的成员函数Show(); void main() A a; B b; a.Show();/ 调用基类A的成员函数Show() b.Show();/ 调用派生类B的成员函数Show() b.Display();如果想调用基类A的成员函数Show(),可以使用作用域限 定 符 “ : : ” : A : : Show(); 从本例可以看出,虽然派生类继承了基类的所有成员函数,但如果派生类某个成员函数的名称和参数与基类成员函数一致(即在派生类中对该成员函数重新进行了定义),则在派生类中调用的成员函数是派生类的成员函数。 请问:如果在派生类B中没有对成员函数Show()重新

38、进行定义,程序运行结果如何? 程序运行结果:程序运行结果:A:ShowB:ShowB:Show 为什么我们经常在现有类的基础上采用继承的方法来定制新类,而不通过直接修改现有类来设计自己的类?除了代码重用的优越性,其主要原因是可能得不到基类的实现源码。继承的重要性! 在利用微软基础类MFC派生自己的类时,我们只需要MFC类声明的头文件(利用#include指令将头文件包含)和含有成员函数目标代码的OBJ文件,并不需要整个MFC类库的实现源码。 l一个派生类对象也属于其基类,因此当程序创建一个派生类对象时,系统首先自动创建一个基类对象。l在调用派生类的构造函数构建派生类对象时,系统首先调用基类的构

39、造函数构建基类对象。当派生类对象的生存期结束时,首先调用派生类的析构函数,然后调用基类的析构函数。 编译器在对程序编译时,首先生成基类构造函数的调用代码,然后生成派生类构造函数的调用代码。 隐式调用和显式调用两种方式: 注意:注意:除非基类有默认的构造函数,否则必须采用显式调用方式。(1)隐式方式隐式方式是指在派生类的构造函数中不指定对应的基类的构造函数,调用的是基类的默认构造函数(即含有缺省参数值或不带参数的构造函数)。 (2)显式方式显式方式是指在派生类的构造函数中指定要调用的基类构造函数,并将派生类构造函数的部分参数值传递给基类构造函数。 设类B是类A的派生类,则派生类B显式方式构造函数

40、的定义形式如下: B:B( ) : A( ). . . / 类B构造函数的实现代码 形参声明中的部分参数,传递给基类构造函数 派生类构造函数形参的名称和类型 l派生类构造函数既初始化派生类的数据成员,又通过基类构造函数初始化其基类的数据成员。l参数表中参数的个数和类型要与基类某个构造函数的形参声明一致。 PointCircle Cylinder 注意:注意: 当基类有多个构造函数时,编译器根据派生类构造函数为基类构造函数提供的参数表来确定调用基类的哪一个构造函数。例例 首先定义类Point,然后定义类Point的派生类Circle,再定义类Circle的派生类Cylinder。 (x, y)(

41、x, y)r(x, y)hr#include class Point/ 定义基类Pointprotected: int x, y;public: Point(int a=0, int b=0) / 含有缺省参数值的构造函数也是默认的构造函数x=a; y=b;coutPoint constructor:x,yendl; ; Point() coutPoint destructor:x,yendl; ;class Circle : public Point/ 定义类Point的派生类protected: int radius;public:/ 显式调用基类的构造函数 Circle(int a=0,

42、 int b=0, int r=0) : Point(a, b) radius=r;coutCircle constructor:radiusx,yendl; ; Circle() coutCircle destructor:radiusx,yendl; ;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; coutCylin

43、der constructor:heightradiusx,yendl; ; Cylinder() coutCylinder destructor:heightradiusx,yendl; ;main() Cylinder cylinder(200, 300, 100, 400); / 调用了类Point、Circle和Cylinder的构造函数Point constructor:200,300Circle constructor:100 200,300Cylinder constructor:400 100 200,300Cylinder destructor:400 100 200,300

44、Circle destructor:100 200,300Point destructor:200,300程序运行结果:程序运行结果:构造函数的执行顺序:构造函数的执行顺序:析构函数的执行顺序:析构函数的执行顺序:Point( )Circle( )Cylinder( )当声明Cylinder对象时 Cylinder( ) Circle( ) Point( )当程序结束时作业:作业:P110-1143-37(2),3-45(上机),3-46class Aclass Bclass Cclass Aclass Bclass C每个派生类只有一个直接基类 单继承一个派生类同时从多个基类派生而来,即有多

45、个直接基类 多重继承 设类B是类A1、A2、An的派生类,多重继承的派生类的定义形式为: class : , , , . . . / 派生类新增加的成员声明列表 ;多 重 继 承 的 派 生 方 式 也 有private、public和protected三种,各基类的派生方式可以不同 class BaseA / 定义基类protected: int a;public: void setA(int);class BaseB / 定义基类protected: int b;public: void setB(int); 定义两个基类void BaseA:setA(int x) a=x;void Ba

46、seB:setB(int x) b=x;int MultiDerived:getAB() return a+b;可以直接访问基类中protected属性成员成员函数的实现程序运行结果:a+b=100二义性错误:二义性错误:编译器无法确定数据成员a是哪一个副本 class Bclass Cclass Dclass ADBACA派生类D的对象中存在间接基类A的两份副本 利用作用域限定符(:)把基类的成员与下一层基类关联起来: d1.B:a=100;或: d1.C:a=100 从路径DBA继承而来 从路径DCA继承而来 缺点:缺点: 浪费了存储空间; 在访问基类的成员时,要求指明访问路径。 大部分情

47、况下不需要保存基类多个相同的副本。 虚基类并不是一种新的类型的类,而是一种派生方式。采用虚基类方式定义派生类,在创建派生类的对象时,类层次结构中虚基类的成员只出现一次,即基类的一个副本被所有派生类对象所共享。 class Bclass Cclass Dclass AD A B C主函数中:d1.a=100; 采用虚基类方式定义派生类的方法是在基类的前面加上关键字virtual,而定义基类时与一般基类完全一样。 节约内存空间; 避免在多重派生类中类成员的不明确性。 作业:作业:P111-1143-37(3),3-48下节讲授的内容 l声明一个派生类的对象的同时也自动声明了一个基类的对象。 3.3

48、小节内容l派生类的对象可以认为是其基类的对象。C+允许一个基类对象的指针指向其派生类的对象 这是实现虚函数的关键l不允许派生类对象的指针指向其基类的对象。l即使将一个基类对象的指针指向其派生类的对象,通过该指针也只能访问派生类中从基类继承的公有成员,不能访问派生类自定义的成员,除非通过强制类型转换将基类指针转换为派生类指针。 例例 程序运行结果为:程序运行结果为:a=100b=200 pb=&a pa-setB()pa-showB() class Apublic: void Show( ) coutShow();class B : public Apublic: void Show( ) co

49、utB:Shown; ; 调用哪一个Show()如果想通过基类指针调用派生类中覆盖的成员函数,只有使用虚函数虚函数。 要将一个成员函数声明为虚函数,只需在定义基类时在成员函数声明的开始位置加上关键字virtual。class Apublic: virtual void Show() coutA:shown; ;class B : public Apublic: void Show() coutDraw()”可能是绘制矩形,也可能是绘制三角形或圆。具体绘制什么图形,取决于pShape所指的对象。l即将函数调用语句与函数代码相关联。l两种联编方式:静态联编和动态联编。静态联编是指编译器在编译阶段就

50、确定了要调用的函数,即早期绑定。动态联编是指在程序执行过程中根据具体情况再确定要调用的函数,即后期绑定。l重载重载采用静态联编方式:虽然函数名相同,但编译器能够根据函数参数类型的不同确定要调用的函数。重载体现出一种静态多态性或编译时多态性。l当通过基类指针调用虚函数虚函数时,C+采用动态联编方式。虚函数体现出一种动态多态性或运行时多态性。 l基于构造函数的特点,不能将构造函数定义为虚函数。声明派生类对象时自动调用基类的构造函数例例 l当撤消派生类的对象时,先调用派生类析构函数,然后自动调用基类析构函数,如此看来析构函数没必要定义为虚函数。但是,假如使用基类指针指向其派生类的对象,而这个派生类对

51、象是用new运算创建的。当程序使用delete运算撤消派生类对象时,这时只调用了基类的析构函数,而没有调用派生类的析构函数。l如果使用虚析构函数,无论指针所指的对象是基类对象还是派生类对象,程序执行时都会调用对应的析构函数。 class Apublic:A() ;/ 构造函数不能是虚函数virtual A() coutA:destructorn; ; / 析构函数是虚函数;class B : public Apublic:B() ;B() coutB:destructorn; ; / 虚析构函数;void main()A *pA=new B;/ . . . . . . delete pA;/

52、先调用派生类B的构造函数,再调用基类A的构造函数 程序运行结果:程序运行结果: B:destructor A:destructor 总结总结:由于使用了虚析构函数,当撤消pA所指派生类B的对象时,首先调用派生类B的析构函数,然后再调用基类A的析构函数。如果析构函数不是虚函数,则得不到下面的运行结果。请读者思考会是什么结果 l抽象类是抽象类是类的一些行为(成员函数)没有给出具体定义的类,即纯粹的一种抽象。l抽象类抽象类只能用于类的继承,其本身不能用来创建对象,抽象类又称为抽象基类。l抽象基类只提供了一个框架,仅仅起着一个统一接口的作用,而很多具体的功能由派生出来的类去实现。l虽然不能声明抽象类的

53、对象,但可以声明指向抽象类的指针。 在一般的类库中都使用了抽象基类,如类CObject就是微软基础类库MFC的抽象基类。l不定义具体实现的成员函数称为纯虚函数纯虚函数。纯虚函数不能被调用,仅起提供一个统一接口的作用。l纯虚函数的声明声明:virtual ()= 0 ;l当基类是抽象类时,只有在派生类中重新定义基类中的所有纯虚函数,该派生类才不会再成为抽象类。 一个类如果满足以下两个条件之一就是抽象类:l至少有一个成员函数不定义具体的实现;l定义了一个protected属性的构造函数或析构函数。纯虚函数纯虚函数纯虚函数/ 定义抽象基类定义抽象基类class CShapepublic:double

54、 r ;double s ;public:CShape(double x) r=x; / 声明纯虚函数 virtual void Area()=0;/ 定义具体的派生类定义具体的派生类class CCircle : public CShapepublic:CCircle(double x):CShape(x) ; / 重新定义虚函数void Area() s=3.14159*r*r; ;main() CCircle circle(48.52); circle.Area(); coutArea=circle.sendl;作业:作业:P111-1143-38(1)、(2),3- 49,3-52(上

55、机)3.5 重载重载l重载重载是C+提供的一个新特性。C+重载分为函数重载和运算符重载,这两种重载的实质是一样的,因为进行运算可以理解为是调用一个函数。l通过使用重载机制,可以对一个函数名(或运算符)定义多个函数(或运算功能),只不过要求这些函数的参数(或参加运算的操作数)的类型有所不同。l重载使C+程序具有更好的可扩充性。x + yX + Y Add(x, y)Add(x, y, z)l函数重载:函数重载:指一组功能类似但函数参数类型(个数)不同的函数可以共用一个函数名。l当C+编译器遇到重载函数的调用语句时,它能够根据不同的参数类型或不同的参数个数选择一个合适的函数。int abs(int

56、 val) return val0 ? val : val;float abs(float val) return (val0) ? val : val;main() int i=100; coutabs(i)endl; / int型 float f=-125.78F; coutabs(f)endl; / float型 在程序中,求绝对值函数的名称相同,但参数类型不同,这时C+编译器自动按参数表的不同来分别联编不同的求绝对值函数。 l不能利用函数返回类型的不同进行函数重载。因为在没有确定调用的是哪个函数之前,不知道函数的返回类型。 long abc(int); float abc(int);l

57、 同样,不能利用引用进行函数重载: void fun(int&); void fun(int); 因为对于下面的调用语句,编译器无法决定调用哪一个函数: fun(i);/ i是一个整型变量 从上面可以看出,一般函数的重载重载使C+程序具有更好的可扩充性。此外,类的成员函数也可以重载,特别是构造函数的重载给C+程序设计带来很大的灵活性。例例 构造函数的重载。class Boxprivate: int height, width, depth;public: Box() height=0; width=0; depth=0; / 避免给成员变量赋不安全的值 Box(int ht, int wd,

58、int dp) / 重载构造函数 height=ht; width=wd; depth=dp; int Volume() return height*width*depth; ; void main() Box box1; Box box2(10, 15, 20); coutVolume1=box1.Volume() , Volume2=box2.Volume()endl;程序运行结果:程序运行结果:Volume1=0,Volume2=3000类Box有两个构造函数。第一个构造函数不带参数,把默认值0赋给对象;第二个构造函数使用参数值初始化创建的对象。 3.5.2 运算符重载运算符重载l运算符

59、重载:运算符重载:指对于不同数据类型的操作数,同一个运算符所代表的运算功能可以不同。l一个运算符定义了一种操作,一个函数也定义了一种操作,其本质是相同的,当程序遇到运算符时会自动调用相应的运算符函数。l虽然重载运算符完成的功能都能够用一个真正的成员函数来实现,但使用运算符重载使程序更易于理解。l与函数重载类似,编译器是根据参加运算的操作数的类型来识别不同的运算。例:例:l对于表达式:10+20 编译器把它看成如下函数调用: int operator+(10, 20);l对于表达式:10.0+20.0 编译器把它看成如下函数调用: float operator+(10.0, 20.0);参加运算

60、的数是单精度实型数参加运算的数是整数 我们可以将字符串operator+看成一个运算符函数名,这些同名的运算符函数根据不同类型的操作数完成不同的加法运算。重载运算符的形式:重载运算符的形式: 重载一个运算符,就是编写一个运算符函数,重载运算符(函数)的原型为: operator();例例 定义复数类型,重载运算符“+”。运算结果的类型要重载的运算符参加运算的操作数例如:c3=c1+c2class Complexpublic: / 公有成员,以便运算符函数(非成员函数)访问 float r;/ 实部 float i;/ 虚部public: Complex(float x=0, float y=0

温馨提示

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

最新文档

评论

0/150

提交评论