类和对象及其封装性.ppt

上传人:小飞机 文档编号:6329508 上传时间:2023-10-17 格式:PPT 页数:143 大小:422KB
返回 下载 相关 举报
类和对象及其封装性.ppt_第1页
第1页 / 共143页
类和对象及其封装性.ppt_第2页
第2页 / 共143页
类和对象及其封装性.ppt_第3页
第3页 / 共143页
类和对象及其封装性.ppt_第4页
第4页 / 共143页
类和对象及其封装性.ppt_第5页
第5页 / 共143页
点击查看更多>>
资源描述

《类和对象及其封装性.ppt》由会员分享,可在线阅读,更多相关《类和对象及其封装性.ppt(143页珍藏版)》请在三一办公上搜索。

1、第三章 类和对象及其封装性,本章要点1 类的定义及其类对象的封装性2 类成员函数的定义3 类对象的定义和使用4 类的构造函数和析构函数5 类对象的动态创建和释放6 类对象的赋值与复制7 与类对象有关的指针8 类的静态成员9 类对象成员、类对象数组和类对象参数10 友元(友元函数、友元成员和友元类)11 类的只读成员函数定义,3.1 类的定义及其类对象的封装性 无论采用哪种程序设计范型所设计的程序都是由数据处理这些数据的操作组成的。程序的运行就是按照特定的结构和流程将操作施加在相应的数据上,完成程序需要实现的功能。在传统设计范型中,数据是使用语言所提供的简单数据类型和构造数据类型(例如 C 语言

2、中的结构类型 struct)定义生成的;而操作是通过过程或函数的形式定义提供的。,在面向对象设计范型中,使用了数据抽象的概念,即数据总是与允许施加它们的操作绑定在一起的。这就要求编程语言能够提供符合数据抽象的预定义数据类型,特别需要提供能构造符合数据抽象用户自定义类型的构造数据类型(例如 C+和 Java 语言中的类类型 class)。程序中的数据和操作都是由按数据抽象封装起来的对象提供的。,3.1.1 C+的类类型定义 在 C+中,用户可以使用类类型关键字 class 定义自己的抽象数据类型。这种定义方法和形式与使用结构体类型关键字 struct 定义数据结构类型十分相似。例如,可以用 st

3、ruct 定义描述学生基本信息的数据结构类型 Student:struct Student int num;char name20;char sex;,同样,可以用 class 定义描述学生基本信息和基本操作的数据类型 Student:class Student int num;char name20;char sex;public:void display()cout“num:”num endl;cout“name:”name endl;cout“sex:”sex endl;,比较两种用户自定义类型,它们的共同之处表现在:类型定义首行的格式相同,由类型关键字(struct 或 class)与

4、自定义类名组成。例如:“struct Student”和“class Student”。类型定义体都使用左花括号“”表示开始,使用右 花括号“”表示结束,并用分号“;”表示整个自定义 类型定义工作完成。使用自定义类型(结构或类)定义类型实体(结构 变量或类对象)的格式相同。在面向对象的程序设计中通常将所有的类型(包括 系统预定义的简单数据类型)实体可以统一称为对 象。例如,可使用自定义结构类型 Student 定义结构 对象:Student stud1,stud2;也可使用自定义类类型 Student 定义类对象:Student stud1,stud2;,在面向对象的程序设计中通常将所有的类型

5、(包括 系统预定义的简单数据类型)实体可以统一称为对 象。例如,可使用自定义结构类型 Student 定义结构 对象:Student stud1,stud2;也可使用自定义类类型 Student 定义类对象:Student stud1,stud2;,二者的不同之处表现在:使用 C 语言的 struct 定义的结构类型的定义体中只 包含数据成员,而使用 class 定义类类型的定义体中 既包含数据成员,还包含了操作这些数据的成员函 数。注意,在 C+中,struct 定义能力被扩展,也可 以 class 定义一样包含操作数据成员的成员函数。结构类型的成员的缺省访问权限均为公有,即可以 从结构对象

6、外直接访问。例如:Student stud1;cout“num:”stud1.num endl;,cout“name:”stud1.name endl;cout“sex:”stud1.sex endl;而类类型的成员的缺省访问权限均为私有,例如,类 Student 中的数据成员就不能从类外直接访问,而 显示这些数据只能通过调用成员函数 display 实现。Student stud1;cout“name:”stud1.name endl;/非法stud1.display();,3.1.2 类类型成员的访问权限 为了实现类对象的封装性(数据隐藏和提供访问接口)类类型定义为类成员提供了私有和公有两

7、种基本访问权限供用户选择。1 私有成员 访问权限:只限于类成员访问。关键字:private 声明或从定义体开始的缺省声 明。例如,下面的 Student 定义与前面的 Student 定义等价的:,class Studentprivate:int num;char name20;char sex;public:void display()cout“num:”num endl;cout“name:”name endl;cout“sex:”sex endl;,私有段:从 private:开始至其它访问权限声明之间 所有成员组成的代码段。例如 Student 定义中从 private:开始到 pub

8、lic:之间的所数据成员。成员种类:数据成员和成员函数。2 公有成员 访问权限:允许类成员和类外的任何访问。关键字:public。公有段:从 public:至其它成员声明之间所有成员 组成的代码段。成员种类:数据成员和成员函数。,使用私有成员来隐藏由类对象操作的数据,然后提供相应的公有成员函数来访问和操作这些数据,而访问和操作这些数据实现细节通常是被隐藏起来的。除了私有和公有两种基本访问权限外,类类型定义还提供了允许类成员和派生类成员访问,而不允许类外访问的保护成员访问权限(protected),以满足实现继承性的需要。,为了使 C+语言所设计程序中的数据都能实现数据抽象,并能与 C 语言设计

9、的程序中的数据兼容,C+仿照类类型定义的功能,对 struct 定义的结构体类型功能进行如下扩展:定义体中也可以包括对数据成员进行处理和操作的 成员函数。添加了与类类型定义相同的成员访问权限声明功 能,但仍然保留了缺省声明表示成员的访问权限为 公用的基本特点。扩展后的 struct 可以定义与类类型效果相同的结构类型,例如:,struct Studentprivate:int num;char name20;char sex;public:void display()cout“num:”num endl;cout“name:”name endl;cout“sex:”sex endl;,该结构类

10、型与先前使用 class 定义的 Student 类型的效果完全相同。请注意,这并不意味着可以用 struct 替代 class,因为使用 class 定义的类类型的缺省私有性质能更方便、安全地实现面向对象程序设计对类对象的要求,因此强烈建议使用 class 建立数据类型;而只有希望所建立类型的全部成员都是公有访问权限时,使用 struct 建立结构类型比较方便。,3.1.3 类类型的构造 类类型定义为所定义的数据类型建立了一个明确的边界,类定义体中的私有成员(数据成员和成员函数)和公有成员函数的实现细节均被封装在此边界内,使得这些类成员和实现细节无法从类对象外被访问,从而受到保护。同时对类公

11、有成员(数据成员和成员函数)的访问和调用又为类对象之间的通讯提供了接口,使得类对象成为一个既访问安全又操作方便的抽象数据实体。下面以一个简单机器人类为例说明类类型的构造:,确定机器人状态的属性:是位置和面对的方向 改变、访问和显示机器人状态的操作:有定位、转向、前进、显示状态等。class Hominoid int dirction;/机器人的方向point location;/机器人的位置 public:void turnLeft();/向左转90度void turnRight();/向右转90度bool advance();/前进一步point location(point loc);/定

12、位bool facingWall();/判断是否面对墙壁void display();/显示当前位置;,bool advance();/前进一步point location(point loc);/定位bool facingWall();/判断是否面对墙壁void display();/显示当前位置;该定义所建立的机器人类的构造可以形象地用下图表示,它很象一个封装好的器件。,directionlocation,Java 的类定义与 C+的类定义在格式上基本相同,但有两点是不一样的:成员的访问权限必须逐个显式说明;类定义结束不要分号“;”。例如:class User private String

13、 name;private int age;public User(String str int yy)name=str;age=yy;返回,3.2 类成员函数的定义1 成员函数的性质 类的成员函数在声明和定义的格式上以及用法和作 用上与一般函数基本一致。但由于成员函数是属于 某一个类的成员,因此它与一般函数的区别表现:作用域在类定义体所确定的边界内,即可以访问 本类的任何成员(私有和公有的数据和函数)。需要根据功能和作用指定成员函数的访问权限,一般情况下,将向类外提供操作功能的成员函数 指定为 public,将只为类内提供服务功能的成员函 数指定为 private,将只为类内和派生类提供服务

14、 功能的成员函数指定为 protected。,2 成员函数的声明和定义形式 在类定义体内定义成员函数的实现代码。这种形 式下,函数定义的首部将起到函数原型的作用,因此无须成员函数定义之前的原型声明。例如:class point int x,y;public:void setpoint(int vx,int vy)x=vx;y=vy;,在类定义体内声明成员函数,而在类定义体外定 义成员函数的实现代码。采用这种定义形式的 时,类定义体外的定义代码必须满足:在成员函数名之前应缀上所属的类名:,“:”是 作用域运算符,以便说明函数的作用域。成员函数定义的首部(函数的返回类型、函数 名和参数表列)必须与

15、在类定义体中声明的该 函数的原型一致。例如:,class point int x,y;public:void setpoint(int,int);void point:setpoint(int vx,int vy)x=vx;y=vy;由于第 种形式不仅可以减少类定义体的代码长 度,使类定义体清晰、可读性好;更重要的是有助 于类的操作接口与操作实现细节相分离,并隐藏细 节。因此,提倡采用该形式定义类成员函数。,3 类的内置(内联)成员函数定义方式:隐式定义 函数定义在类定义体中,此时只 要函数的实现代码符合内置函数的定义要求,该 成员函数就会自动被定义内置函数,而说明内置 函数的关键字 inli

16、ne 可以忽略。例如:class point int x,y;public:void setpoint(int vx,int vy)/内置函数 x=vx;y=vy;其中成员函数 setpoint 定义的首部与加缀 inline 的 首部“inline void setpoint(int vx,int vy)”等效。,显式定义 函数声明在类定义体中,而函数 定义在类定义体外,此时函数定义的首部必须冠 以关键字 inline 说明此函数是内置的。例如:class pointint x,y;public:inline void setpoint(int,int);/内置函数声明;inline voi

17、d point:setpoint(int vx,int vy)/内置函数定义x=vx;y=vy;,需要特别注意的是:由于调用内置函数需要将内置函数的目标代码复制到它被调用的位置,因此编译器在进行内置函数的调用编译时,必须能获得被调内置函数的目标代码。这就需要在调用内置函数的源代码文件中必须包含被调用内置函数的定义代码的源代码文件。也就是说,如果某个被调用的内置成员函数定义在类定义体中,该类定义体代码被保存在一个头文件中,例如“student.h”,则调用该内置函数的源文件应添加预编译命令#include“student.h”。如果该内置函数定义在类定义体外,代码包含在类实现文件中,例如“stu

18、dent.cpp”,则调用该内置函数的源文件应添加预编译命令#include“student.cpp”。,4 成员函数的存储空间 从类类型的定义不难看出,类的数据成员(对象属 性)中保存的数据值代表了类对象的状态,决定了 该类的不同对象的差别,因此当类对象创建时,每 个类对象都必须独占一份(个数相同、类型相同)数据成员存储空间,用于保存区别于其他对象的状 态;而类的成员函数描述了该类所有对象的统一行 为操作,而操作结果(行为表现)的差异取决于不 同对象的状态,因此,成员函数的运行代码被存储 在与数据成员存储空间不同的代码空间中,被该类 的所有对象共享。,例如:由于语句 Student stud

19、1,stud2,stud3;执行所创 建的 3 个 Student 对象在程序运行空间中占用内存的 大小和位置的状态示意如下:返回,3.3 类对象的定义和使用3.3.1 类与对象的关系 类是一组具有相同属性和行为的对象的抽象,是创 建对象的模板,是用户使用 class 创建的自定义类 型。类一旦创建,其作用可与系统预定义类型(例 如,int、double 等)类比。对象是类的实例,创建类的对象可以与创建预定义 类型的变量(例如,int x;、double d;等)类比。类只是提供了该类对象的创建和使用的方法和规 则,因此类本身不占用内存。创建类对象时将按类 定义提供的方法和规则,在内存中为类对

20、象分配空 间,因此,封装是对类对象而言的。,3.3.2 类对象的定义方法:1 先创建类类型,使用时再定义对象 大多数情况均采用该方法定义类对象。这样创建的 类对象的生存周期取决于创建的位置。例如:class Student;void main()Student Zhang,Li;/创建 Student 局部对象 虽然 C+也允许将上述的类对象定义语句写成:class Student Zhang,Li;(C 风格)但不能体现 C+面 向对象的设计风格,且不方便简洁,所以不提倡。,2 在创建类类型的同时定义对象。使用这种方法定义的类对象的生存周期取决于类类 型的创建位置。例如:class Stud

21、entint num;char name20;char sex;public:void display()cout“num:”num endl;cout“name:”name endl;cout“sex:”sex endl;Zhang,Li;,3 不出现类名,直接定义对象。使用这种方法定义类对象的类类型一般在程序中只 出现和使用一次。例如,创建一个描述人员信息的 类类型 person 中包含了一个联系信息属性成员 touchInfo,该属性是一个由人员的通讯地址、邮政编 码、电话号码、e-mail 等信息和相应的处理操作构 成的类类型对象,而该类类型只在定义 touchInfo 属 性出现和使

22、用一次。此时就可以采用本方法在类类 型 person 中定义 touchInfo 属性:,class personclass/无须类名string Addess;string postCode;string poneNum;string E-mail;public:touchInfo;,3.3.3 类对象的使用 类的使用是通过首先创建类对象,然后引用类对象的公有成员(访问数据成员或调用成员函数)达到对该类的使用。依据类对象的创建方式不同,类的使用形式可以分为三种:1 通过对象名和成员运算符访问对象成员一般形式:对象名.成员名;其中成员名必须是对象名所指明对象的所属类的公有数据成员名或公有成员函

23、数名。例如:,class point int x,y;public:setpoint(int vx,int vy)x=vx;y=vy;void fun()point pt;/创建 point 类对象 ptpt.setpoint(10,10);/给 point 对象 pt 的坐标 x,y 赋值pt.x=50;/错误,不能访问私有成员.代码 pt.setpoint(10,10)实际是 pt.point:setpoint(10,10)的缩写,表明通过对象访问对象名所指示的类成员。,2 通过指向对象的指针访问对象成员一般形式:指针名-成员名;其中成员名必须是指针所指对象的所属类的公有数据成员名或公有成

24、员函数名。例如:class point int x,y;public:setpoint(int vx,int vy)x=vx;y=vy;void fun()point*p=new point;/p 指向动态创建的 point 对象p-setpoint(10,10);/给 p 所指对象的坐标 x,y 赋值.,3 通过对象的引用访问对象成员一般形式:对象引用名.成员名;其中成员名必须是对象引用名所引用对象的所属类的公有数据成员名或公有成员函数名。例如:class point int x,y;public:setpoint(int vx,int vy)x=vx;y=vy;void fun()poin

25、t pt,/给 p 引用的对象的坐标 x,y 赋值.,3.3.4 成员名解析 由于类成员作用域在该类定义体所限定的边界内,因此,不同类中具有同名的成员是不会产生二义性。例如:class realSet/定义一个实数集合类public:void print();class intSet/定义一个整数集合类public:void print();,void fun()intSet is;realSet rs;is.print();/调用 intSet 类中的 print()函数rs.print();/调用 realSet 类中的 print()函数显然不会引起二义性错误。返回,3.4 构造函数使用

26、类定义对象时,需要一种操作,使所定义的对象与类的定义域相关。实现这一操作的成员函数称为构造函数,该函数要完成的操作包括:依据类数据成员的个数和类型为对象分配内存;根据需要为对象的数据成员进行必要的初始化。构造函数是类必须拥有的特殊成员函数,该函数从定义形式到使用场合和方法上都与一般成员函数有所区别,这些差异表现在以下几个方面:,构造函数名必须与类名相同,否则编译器将会把它 当作一般成员函数对待。例如:class Studentpublic:Student();Student:Student(),构造函数没有返回值,因此,声明和定义构造函数 时都不能说明它的返回类型;构造函数的功能是将对象初始化

27、,因此构造函数一 般只对数据成员进行初始化和必要的辅助操作,而不提倡做与初始化无关的操作。系统总会为类提供一个隐含的缺省构造函数。该构 造函数实际上是一个空定义体函数,因此只能为对 象分配空间而不能为数据成员进行初始化。在大多数情况下,数据成员的初始化操作是十分必 要的,因此通常需要显式定义构造函数。构造函数 一旦显式定义,缺省构造函数将被覆盖。,在程序运行过程中,类对象是在进入其作用域时才 被创建的。也就是说,此时类对象的构造函数被调 用。构造函数在类对象创建时由系统自动执行,不需要 用户调用,也不能由用户调用。例如:Student stud1;/系统调用构造函数创建 stud1stud1.

28、Student();/企图用一般成员函数的调用方法/调用构造函数,因此是错误的。不能为构造函数定义函数指针,也不能获取构造函 数的调用地址。基类的构造函数不能被派生类继承。构造函数不能声明为虚函数。,例3-1 定义一个整数队列类,使用由系统隐含提供的缺省构造函数创建整数队列,并测试队列功能。1 问题分析 用例分析 类图描述,向队列中装入整数,从队列中取出整数,2 详细设计 类设计 qurue 类 类定义 class queue int q100;/队列空间 int head,tail;/队列头、尾指示 public:void qput(int i);/队列插入操作 int qget();/队列

29、取出操作;,算法描述 成员函数 qput 的 N-S 流程图:成员函数 qget 的 N-S 流程图:,显示队列满,trail=trail+1,数据插入队尾,显示队列空,head=head+1 从队头取数据,移动队列数据,修改队列指针tail=tail head;head=0;,类对象创建和使用main 函数的 N-S 流程图:,使用 queue 类创建实例 a,b,向队列实例 a,b 中分别插入:10,20 和 20,19,从队列实例 a,b 中分别顺序取出数据,并显示,3.4.1 参数化的构造函数 与其他成员函数一样,构造函数也可以有参数。通过这些参数为类对象的数据成员传递初值。例如:cl

30、ass pointint x,y;public:point(int vx,int vy);/声明带参数的构造函数void offset(int ax,int ay);,point:point(int vx,int vy)x=vx;/用传递来的实参对 x,y 赋初值y=vy;main()point p(10,20);/定义对象,并传递初值/注意,不要将使用参数创建类对象的代码写成:point p=point(10,20);,3.4.2 构造函数的重载在一个类中允许定义多个参数不同构造函数,即构造函数重载。这样就为在不同情况下创建对象的特定初始化需要提供了实现手段。也就是说,在类对象定义时,编译器

31、可以依据创建对象所需要的参数差异确定调用构造函数的哪一个版本来创建类对象。例3-1-1 定义一个有两个构造函数的类,并使用不同构造函数定义对象。,3.4.3 使用缺省参数值的构造函数 与其他函数一样,构造函数的参数也可以具有缺省值,表示类对象的某些属性在大多数情况下是预先可以确定的缺省状态,例如计数器的初值一般为“0”、战士的性别多数为“男”、大学教师的学位一般为“硕士”等。构造函数的缺省参数值的定义和使用规则与其他带缺省参数值的函数相同。例3-1-2 描述了如何声明,定义和使用带有缺省参数值的类构造函数。,归纳构造函数使用缺省参数值的编程要点是:指定缺省参数值只能在构造函数的声明中,而不能出

32、现在构造函数定义的首部。构造函数定义在类定义体中的情况除外。函数声明中的参数可以省略参数名,此时指定缺省参数值的格式为:类型名=缺省值。例如:Box(int=10,int=10,int=10);如果构造函数的全部参数都指定了缺省值,应该避免再定义一个无参数的构造函数。因为在定义构造函数时,编译器会认为可能是重复定义。例如:Box();Box(int=10,int=10,int=10);,更重要的是定义类对象时,遇到如下情况:Box box1;编译器无法确定调用哪一个构造函数版本来创建类对象。如果构造函数的全部参数都指定了缺省值,就容易在重载构造函数时造成二义性。例如:Box(int=10,in

33、t=10,int=10);Box();Box(int,int);因此,应避免全部参数都指定了缺省值。例如:Box();Box(int,int=10,int=10);Box(int,int);,3.4.4 用参数初始化表对数据成员初始化 所谓参数初始化是指系统在为类对象的各个数据成员分配内存空间的同时能按照用户通过参数指定的值为数据成员赋值,而不是在各个数据成员的内存空间分配完成后,再对它们进行赋值。这就需要通过一种语法格式,即参数初始化表,使编译器能按照上述要求实现对类对象的各个数据成员的初始化。构造函数参数初始化表的一般形式为::基类初始化列表,属性初始化列表,其中基类初始化列表只有在派生类

34、的构造函数初始化表才会存在,这一部分将在第五章中介绍。属性初始化列表由若干个属性初始化项组成,项间用“,”隔开:属性初始化项1,属性初始化项2,属性初始化项n每个属性初始化项的一般格式为:属性名(参数列表)不难看出,属性初始化项的含义是调用相应的属性类的具有参数的构造函数用于属性对象的创建和赋初值操作。,这从另一个角度告诉我们,一个构造函数的定义中没有出现初始化表意味着使用了隐含的初始化表,该表的功能是分别调用了相应类的无参数(或有缺省参数值)构造函数完成类对象的基类部分和各个属性对象创建和赋初值。例3-1-3 是将例3-1-1中的类构造函数改写为使用参数初始化表实现类对象各数据成员的初始化。

35、虽然两个实例中对类对象的数据成员初始化的结果是完全相同的,但两种初始化方法对数据成员的赋值的时间和方法是完全不同的。,在构造函数定义中使用初始化表另一个非常重要的原因就是对于类对象的常数据成员、引用数据成员的初始化就必须在创建的同时进行赋值操作,而不能在函数体中进行赋值。例如:class A public:A(int i);const intA:A(int i):a(i),ref(a),3.4.5 拷贝构造函数 拷贝构造函数是一个特定的构造函数。该构造函数与其他构造函数在形式上的差别仅在于函数的参数必须是同类型对象的常引用。拷贝构造函数的原型格式如下:类型名(const 类型名拷贝构造函数的功

36、能是创建一个新对象,并将参数所引用对象的各个数据成员值复制到新对象的对应的数据成员域中。,系统会为每个类缺省定义一个拷贝构造函数,也允许用户定义一个拷贝构造函数,用以取代缺省的拷贝构造函数。一般情况下,使用系统定义的缺省拷贝构造函数就可以满足类对象的复制操作,但在有些情况下,用户必须定义自己的拷贝构造函数。例如:class string int length;char*str;/指针数据成员public:string(int len);,string:string(int len)length=len;str=new charlen+1;/指针数据成员指向动态分配的内存空间main()stri

37、ng s1(10);/创建一个 string 对象 s1string s2(s1);/复制 s1 到新 string 对象 s2,在这种情况下,s1 和 s2 的指针数据成员 str 指向了同一内存空间,使得通过 str 对该内存空间的任何操作都不能保持应有的独立性。更严重的是当 s1 和 s2 之中有一个被撤消时,在堆中分配的内存空间被撤消回收,使得另一个 string 对象的指针数据成员 str 成为无效指针,任何通过该指针的操作均为非法操作,会导致严重的运行错误。造成这一问题的原因是系统提供的缺省拷贝构造函数不能复制 string 对象。因此,在这种情况下必须定义自己的拷贝构造函数:,s

38、tring:string(const string Java 没有 C+那种含义的指针,也没有拷贝构造函数。同时,对象的撤消是由垃圾收集器完成的,因此也不会产生像 C+中那样的问题。当然在 Java 中也可以用赋值运算符“=”来进行对象赋值,但是,这并不意味着一个简单赋值操作所具有的直觉含义。例如:,class Userpublic string name;class Test public static void main(String args)User u1=new User(“ariel”,112);(u1.name);/arielUser u2=u1;u2.name=“muriel”

39、;(u1.name);/muriel,显然,这里的 u2=u1 只是对对象引用的复制。由于 u1 和 u2 引用同一个 User 对象,所以才会导致修改 u2.name 实际上等价于 对 u1.name 的修改(这与 C+中两个指针指向同一个对象的情况类似)。如果要完成直觉意义上的复制,就必须通过实现 User 类的克隆接口 Cloneable 后,调用逐字节复制的克隆函数 clone()完成。例如:,class User implements Cloneable public string name;class Test public static void main(String args

40、)User u1=new User(“ariel”,112);(u1.name);/arielUser u2=(User)u1.clone();u2.name=“muriel”;(u1.name);/ariel,3.5 析构函数对象撤消时,也需要一种操作,使被撤消的对象从程序的数据区中合法消失。实现这一操作的成员函数称为析构函数,该函数要完成的操作包括:回收被撤消对象数据成员所占用的内存;根据需要完成回收被撤消对象数据成员所占内存之 前的必要操作。析构函数也是类必须拥有的特殊成员函数,该函数从定义形式到使用场合和方法上都与构造函数相似,主要特点表现在以下几个方面:,析构函数名必须是类名加字符“

41、”前缀,否则编译器将会把它当作一般成员函数对待。例如:class Studentpublic:Student();Student:Student(),析构函数没有返回值,因此,声明和定义析构函数 时都不能说明它的返回类型;系统总会为类提供一个隐含的缺省析构函数。该析 构函数实际上是一个空定义体函数,因此只能撤消 回收对象所占用的空间。如果在对象被撤消之前无须做必要的预处理操作,则可以放心使用缺省析构函数。但在有些情况下,则必须定义自己析构函数。析构函数一旦显式定义,缺省析构函数将被覆盖。例如:,class string int length;char*contents;public:strin

42、g(char*s);/声明构造函数string();/声明析构函数;类 string 的对象在撤消之前需要先检查指针类型属性 contents 是否指向有效的内存空间,如果是,则应回收所占用的内存空间。因此,必须重新定义析构函数,取代系统隐含定义的缺省析构函数。string 类的构造函数和析构函数的操作可以按如下定义:,string:string(char*s)/定义构造函数 if(s)length=strlen(s);contents=new charlength+1;/分配存储 strcpy(contents,s);/字串赋值 else length=0;contents=0;/设置指针数

43、据成员为空,string:string()/定义析构函数 if(contents)delete contents;/释放 contents 指向的内存空间 在程序运行过程中,类对象是在退出其作用域时才 被析构的。也就是说,此时类对象的析构函数被调 用。析构函数在类对象撤消时由系统自动执行,不需要 用户调用,也不能由用户直接调用。,不能为析构函数定义函数指针,也不能获取析构函 数的调用地址。基类的析构函数不能被派生类继承。析构函数可以声明为虚函数,并且提倡声明为虚函 数(详细原因在第六章 运行多态性中讲述)。返回,3.6 对象的动态创建和释放 与预定义类型一样,自定义类型也可以使用运算符new

44、动态创建对象和使用运算符 delete 撤消类对象。例3-2 描述了动态创建、撤消和使用 point 类对象。注意:1 使用无参数或具有缺省参数值的构造函数动态创建类对象(即创建对象时不传递初始值)的格式为:new 类型名;例如,point*p=new point;而不应写成:new 类型名();例如,point*p=new point();上述格式与动态创建系统预定义类型变量的格式完全一致(预定义类型无缺省初始值)。例如:int*p=new int;,2 使用有参数的构造函数动态创建类对象(即创建对象时传递初始值)的格式为:new 类型名(初始值);例如,point*p=new point(

45、10,20);上述格式与动态创建系统预定义类型变量并传递初始值的格式完全一致。例如:int*p=new int(10);,例3-3 通过构造函数对对象数组进行初始化。对象数组初始化的方法一般有两种:1 使用缺省构造函数创建对象数组后,调用一个专门用于初始化的成员函数对数组中的每个对象分别进行初始化。该方法虽然必须分两步完成对象数组的创建和初始化,但可以将对象数组中的元素初始化为任意值。2 定义一个带缺省值参数的构造函数。使得在创建对象数组的同时,由构造函数的缺省参数值完成数组中的每个对象对象的初始化。使用该方法创建和初始化对象数组简单、方便,但只能将对象数组中的每个元素初始化为固定的缺省值。返

46、回,3.7 对象的赋值与复制1 对象的赋值对象的赋值只能发生在同类型对象之间的,这与系统预定义类型变量的赋值是一致的。对象赋值的一般格式为:对象名1=对象名2;赋值操作是由赋值运算符“=”完成的,该运算符(函数)的功能是将对象名2 所指示对象的各个数据成员值依次传递给对象名1 所指示对象的各个数据成员,使对象1 与对象2 完全相同。,系统会为每一个自定义类型自动添加一个隐含的缺省赋值运算符,因此一般的自定义类型不必显式定义赋值运算符。但如果类定义中包含有指针类属性(拷贝构造函数中已经讨论这种情况),则必须显式定义赋值运算符用于取代隐含的缺省赋值运算符。如何定义赋值运算符将在第五章中讨论。例3-

47、2-1 描述了使用缺省赋值运算符完成对象的赋值操作。,2 对象的复制对象的复制是指按照一个已经存在的对象创建一个与该对象完全相同的新对象。显然对象的复制操作是由类的拷贝构造函数完成的,复制一个已有对象的一般形式为:类型名 对象2(对象1);例如,Box box2(box1);在 C+中上述复制也适用于预定义类型,即每个预定义类型都有一个隐含的拷贝构造函数。例如,int a(10),b(a);,C+还提供了另外一种方便用户的复制表达式,即用赋值运算符代替括号调用类的拷贝构造函数:类型名 对象2=对象1;例如,Box box2=box1;显然,这种复制形式与预定义类型变量赋值定义形式是一致的。例如

48、,int a=10,b=a;程序中需要对象复制操作的情况有三种:用户需要定义与已有对象完全一致的新对象。函数调用时,系统需要复制被传递的实参对象。函数返回时,系统会复制被返回的操作结果。返回,3.8 与对象有关的指针指针变量可以用于指向任何预定义类型变量,当然也可以指向自定义类型对象。不仅如此,指针变量还可以指向对象的类成员。1 指向对象的指针定义指向对象的指针变量的一般形式:类型名*对象指针名;例如,Box*pt;显然,这与定义预定义类型指针的形式是完全一致的。同样,指针的使用形式也是相同的。例如:pt=new Box;pt-volume();/与(*pt).volume();等价,2 指向

49、对象成员的指针对象的成员有数据成员和成员函数两种,因此指向对象成员的指针也有两种。指向数据成员的指针 定义指向数据成员的指针变量的一般形式:类型名*指针变量名;显然,这与定义预定义类型指针是完全一致的。同样,指针的使用形式也是相同的。例如:class Time public:int hour,minute,sec;void show();,void Time:show()cout hour“:”minute“:”sec endl;Time time;int*p;p=注意,指针只能指向公有数据成员。,指向对象成员函数的指针 定义指向成员函数的指针变量的一般表达式:类型名(类名:*函数指针名)(参

50、数列表);例如,void(Time:*pf)();为函数指针赋值,指向成员函数的一般表达式:函数指针名=,3 this 指针 this 指针是类成员函数拥有的一个隐含指针,它指向 该成员函数被调用时,操作所施加的类对象地址,实现不同对象的相同行为的表现差异。如图所示:,成员函数,this,成员函数通过 this 指针可以访问所指对象中的类成员,数据成员的访问格式可写为:this-数据成员名成员函数的访问格式可写成:this-成员函数名(参数列表)例如:,class exth int i;public:void load(int val)this-i=val;/与 i=val;等价int get

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号