类和对象及其封装性精选文档.ppt

上传人:石*** 文档编号:70751969 上传时间:2023-01-27 格式:PPT 页数:143 大小:4.22MB
返回 下载 相关 举报
类和对象及其封装性精选文档.ppt_第1页
第1页 / 共143页
类和对象及其封装性精选文档.ppt_第2页
第2页 / 共143页
点击查看更多>>
资源描述

《类和对象及其封装性精选文档.ppt》由会员分享,可在线阅读,更多相关《类和对象及其封装性精选文档.ppt(143页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

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

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

3、class 定义自己的抽象数据类型。这种定义方法和形式与使用结构体类型关键字 struct 定义数据结构类型十分相似。例如,可以用 struct 定义描述学生基本信息的数据结构类型 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

4、 endl;cout “sex:”sex endl;本讲稿第六页,共一百四十三页比较两种用户自定义类型,它们的共同之处表现在:类型定义首行的格式相同,由类型关键字(struct 或 class)与自定义类名组成。例如:“struct Student”和 “class Student”。类型定义体都使用左花括号“”表示开始,使用右 花括号“”表示结束,并用分号“;”表示整个自定义 类型定义工作完成。使用自定义类型(结构或类)定义类型实体(结构 变量或类对象)的格式相同。在面向对象的程序设计中通常将所有的类型(包括 系统预定义的简单数据类型)实体可以统一称为对 象。例如,可使用自定义结构类型 St

5、udent 定义结构 对象:Student stud1,stud2;也可使用自定义类类型 Student 定义类对象:Student stud1,stud2;本讲稿第七页,共一百四十三页在面向对象的程序设计中通常将所有的类型(包括 系统预定义的简单数据类型)实体可以统一称为对 象。例如,可使用自定义结构类型 Student 定义结构 对象:Student stud1,stud2;也可使用自定义类类型 Student 定义类对象:Student stud1,stud2;本讲稿第八页,共一百四十三页二者的不同之处表现在:使用 C 语言的 struct 定义的结构类型的定义体中只 包含数据成员,而使

6、用 class 定义类类型的定义体中 既包含数据成员,还包含了操作这些数据的成员函 数。注意,在 C+中,struct 定义能力被扩展,也可 以 class 定义一样包含操作数据成员的成员函数。结构类型的成员的缺省访问权限均为公有,即可以 从结构对象外直接访问。例如:Student stud1;cout “num:”stud1.num endl;本讲稿第九页,共一百四十三页cout “name:”stud1.name endl;cout “sex:”stud1.sex endl;而类类型的成员的缺省访问权限均为私有,例如,类 Student 中的数据成员就不能从类外直接访问,而 显示这些数据只

7、能通过调用成员函数 display 实现。Student stud1;cout “name:”stud1.name endl;/非法stud1.display();本讲稿第十页,共一百四十三页3.1.2 类类型成员的访问权限类类型成员的访问权限 为了实现类对象的封装性(数据隐藏和提供访问接口)类类型定义为类成员提供了私有和公有两种基本访问权限供用户选择。1 私有成员 访问权限:只限于类成员访问。关键字:private 声明或从定义体开始的缺省声 明。例如,下面的 Student 定义与前面的 Student 定义等价的:本讲稿第十一页,共一百四十三页class Studentprivate:i

8、nt num;char name20;char sex;public:void display()cout “num:”num endl;cout “name:”name endl;cout “sex:”sex endl;本讲稿第十二页,共一百四十三页 私有段:从 private:开始至其它访问权限声明之间 所有成员组成的代码段。例如 Student 定义中从 private:开始到 public:之间的所数据成员。成员种类:数据成员和成员函数。2 公有成员 访问权限:允许类成员和类外的任何访问。关键字:public。公有段:从 public:至其它成员声明之间所有成员 组成的代码段。成员种类

9、:数据成员和成员函数。本讲稿第十三页,共一百四十三页 使用私有成员来隐藏由类对象操作的数据,然后提供相应的公有成员函数来访问和操作这些数据,而访问和操作这些数据实现细节通常是被隐藏起来的。除了私有和公有两种基本访问权限外,类类型定义还提供了允许类成员和派生类成员访问,而不允许类外访问的保护成员访问权限(protected),以满足实现继承性的需要。本讲稿第十四页,共一百四十三页 为了使 C+语言所设计程序中的数据都能实现数据抽象,并能与 C 语言设计的程序中的数据兼容,C+仿照类类型定义的功能,对 struct 定义的结构体类型功能进行如下扩展:定义体中也可以包括对数据成员进行处理和操作的 成

10、员函数。添加了与类类型定义相同的成员访问权限声明功 能,但仍然保留了缺省声明表示成员的访问权限为 公用的基本特点。扩展后的 struct 可以定义与类类型效果相同的结构类型,例如:本讲稿第十五页,共一百四十三页 struct Studentprivate:int num;char name20;char sex;public:void display()cout “num:”num endl;cout “name:”name endl;cout “sex:”sex endl;本讲稿第十六页,共一百四十三页该结构类型与先前使用 class 定义的 Student 类型的效果完全相同。请注意,这并

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

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

13、facingWall();/判断是否面对墙壁void display();/显示当前位置;本讲稿第十九页,共一百四十三页bool advance();/前进一步point location(point loc);/定位bool facingWall();/判断是否面对墙壁void display();/显示当前位置;该定义所建立的机器人类的构造可以形象地用下图表示,它很象一个封装好的器件。本讲稿第二十页,共一百四十三页directionlocation.turnLeftdislayturnRightfacingWalladvancelocation属性操作边界本讲稿第二十一页,共一百四十三页

14、Java 的类定义与 C+的类定义在格式上基本相同,但有两点是不一样的:成员的访问权限必须逐个显式说明;类定义结束不要分号“;”。例如:class User private String name;private int age;public User(String str int yy)name=str;age=yy;返回本讲稿第二十二页,共一百四十三页3.2 类成员函数的定义类成员函数的定义1 成员函数的性质 类的成员函数在声明和定义的格式上以及用法和作 用上与一般函数基本一致。但由于成员函数是属于 某一个类的成员,因此它与一般函数的区别表现:作用域在类定义体所确定的边界内,即可以访问 本

15、类的任何成员(私有和公有的数据和函数)。需要根据功能和作用指定成员函数的访问权限,一般情况下,将向类外提供操作功能的成员函数 指定为 public,将只为类内提供服务功能的成员函 数指定为 private,将只为类内和派生类提供服务 功能的成员函数指定为 protected。本讲稿第二十三页,共一百四十三页2 成员函数的声明和定义形式 在类定义体内定义成员函数的实现代码。这种形 式下,函数定义的首部将起到函数原型的作用,因此无须成员函数定义之前的原型声明。例如:class point int x,y;public:void setpoint(int vx,int vy)x=vx;y=vy;本讲

16、稿第二十四页,共一百四十三页 在类定义体内声明成员函数,而在类定义体外定 义成员函数的实现代码。采用这种定义形式的 时,类定义体外的定义代码必须满足:在成员函数名之前应缀上所属的类名:,“:”是 作用域运算符,以便说明函数的作用域。成员函数定义的首部(函数的返回类型、函数 名和参数表列)必须与在类定义体中声明的该 函数的原型一致。例如:本讲稿第二十五页,共一百四十三页class point int x,y;public:void setpoint(int,int);void point:setpoint(int vx,int vy)x=vx;y=vy;由于第 种形式不仅可以减少类定义体的代码长

17、 度,使类定义体清晰、可读性好;更重要的是有助 于类的操作接口与操作实现细节相分离,并隐藏细 节。因此,提倡采用该形式定义类成员函数。本讲稿第二十六页,共一百四十三页3 类的内置(内联)成员函数定义方式:隐式定义 函数定义在类定义体中,此时只 要函数的实现代码符合内置函数的定义要求,该 成员函数就会自动被定义内置函数,而说明内置 函数的关键字 inline 可以忽略。例如:class point int x,y;public:void setpoint(int vx,int vy)/内置函数 x=vx;y=vy;其中成员函数 setpoint 定义的首部与加缀 inline 的 首部“inli

18、ne void setpoint(int vx,int vy)”等效。本讲稿第二十七页,共一百四十三页 显式定义 函数声明在类定义体中,而函数 定义在类定义体外,此时函数定义的首部必须冠 以关键字 inline 说明此函数是内置的。例如:class pointint x,y;public:inline void setpoint(int,int);/内置函数声明;inline void point:setpoint(int vx,int vy)/内置函数定义x=vx;y=vy;本讲稿第二十八页,共一百四十三页 需要特别注意的是:由于调用内置函数需要将内置函数的目标代码复制到它被调用的位置,因此

19、编译器在进行内置函数的调用编译时,必须能获得被调内置函数的目标代码。这就需要在调用内置函数的源代码文件中必须包含被调用内置函数的定义代码的源代码文件。也就是说,如果某个被调用的内置成员函数定义在类定义体中,该类定义体代码被保存在一个头文件中,例如“student.h”,则调用该内置函数的源文件应添加预编译命令#include“student.h”。如果该内置函数定义在类定义体外,代码包含在类实现文件中,例如“student.cpp”,则调用该内置函数的源文件应添加预编译命令#include“student.cpp”。本讲稿第二十九页,共一百四十三页4 成员函数的存储空间 从类类型的定义不难看出

20、,类的数据成员(对象属 性)中保存的数据值代表了类对象的状态,决定了 该类的不同对象的差别,因此当类对象创建时,每 个类对象都必须独占一份(个数相同、类型相同)数据成员存储空间,用于保存区别于其他对象的状 态;而类的成员函数描述了该类所有对象的统一行 为操作,而操作结果(行为表现)的差异取决于不 同对象的状态,因此,成员函数的运行代码被存储 在与数据成员存储空间不同的代码空间中,被该类 的所有对象共享。本讲稿第三十页,共一百四十三页 例如:由于语句 Student stud1,stud2,stud3;执行所创 建的 3 个 Student 对象在程序运行空间中占用内存的 大小和位置的状态示意如

21、下:返回程序数据区程序代码区Student:Student()的运行代码Student:Student()的运行代码Student:display()的运行代码num name sexstud1num name sexstud2num name sexstud3本讲稿第三十一页,共一百四十三页3.3 类对象的定义和使用类对象的定义和使用3.3.1 类与对象的关系类与对象的关系 类是一组具有相同属性和行为的对象的抽象,是创 建对象的模板,是用户使用 class 创建的自定义类 型。类一旦创建,其作用可与系统预定义类型(例 如,int、double 等)类比。对象是类的实例,创建类的对象可以与创建

22、预定义 类型的变量(例如,int x;、double d;等)类比。类只是提供了该类对象的创建和使用的方法和规 则,因此类本身不占用内存。创建类对象时将按类 定义提供的方法和规则,在内存中为类对象分配空 间,因此,封装是对类对象而言的。本讲稿第三十二页,共一百四十三页3.3.2 类对象的定义方法:类对象的定义方法:1 先创建类类型,使用时再定义对象 大多数情况均采用该方法定义类对象。这样创建的 类对象的生存周期取决于创建的位置。例如:class Student ;void main()Student Zhang,Li;/创建 Student 局部对象 虽然 C+也允许将上述的类对象定义语句写成

23、:class Student Zhang,Li;(C 风格)但不能体现 C+面 向对象的设计风格,且不方便简洁,所以不提倡。本讲稿第三十三页,共一百四十三页2 在创建类类型的同时定义对象。使用这种方法定义的类对象的生存周期取决于类类 型的创建位置。例如:class Studentint num;char name20;char sex;public:void display()cout “num:”num endl;cout “name:”name endl;cout “sex:”sex 成员名;其中成员名必须是指针所指对象的所属类的公有数据成员名或公有成员函数名。例如:class point

24、 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()point pt

25、,&p=pt;/p 引用 point 对象 ptp.setpoint(10,10);/给 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 类中的

26、 print()函数rs.print();/调用 realSet 类中的 print()函数显然不会引起二义性错误。返回本讲稿第四十二页,共一百四十三页3.4 构造函数构造函数使用类定义对象时,需要一种操作,使所定义的对象与类的定义域相关。实现这一操作的成员函数称为构造函数,该函数要完成的操作包括:依据类数据成员的个数和类型为对象分配内存;根据需要为对象的数据成员进行必要的初始化。构造函数是类必须拥有的特殊成员函数,该函数从定义形式到使用场合和方法上都与一般成员函数有所区别,这些差异表现在以下几个方面:本讲稿第四十三页,共一百四十三页 构造函数名必须与类名相同,否则编译器将会把它 当作一般成员

27、函数对待。例如:class Studentpublic:Student();Student:Student()本讲稿第四十四页,共一百四十三页 构造函数没有返回值,因此,声明和定义构造函数 时都不能说明它的返回类型;构造函数的功能是将对象初始化,因此构造函数一 般只对数据成员进行初始化和必要的辅助操作,而不提倡做与初始化无关的操作。系统总会为类提供一个隐含的缺省构造函数。该构 造函数实际上是一个空定义体函数,因此只能为对 象分配空间而不能为数据成员进行初始化。在大多数情况下,数据成员的初始化操作是十分必 要的,因此通常需要显式定义构造函数。构造函数 一旦显式定义,缺省构造函数将被覆盖。本讲稿第

28、四十五页,共一百四十三页 在程序运行过程中,类对象是在进入其作用域时才 被创建的。也就是说,此时类对象的构造函数被调 用。构造函数在类对象创建时由系统自动执行,不需要 用户调用,也不能由用户调用。例如:Student stud1;/系统调用构造函数创建 stud1stud1.Student();/企图用一般成员函数的调用方法/调用构造函数,因此是错误的。不能为构造函数定义函数指针,也不能获取构造函 数的调用地址。基类的构造函数不能被派生类继承。构造函数不能声明为虚函数。本讲稿第四十六页,共一百四十三页例例3-13-1 定义一个整数队列类,使用由系统隐含提供的缺省构造函数创建整数队列,并测试队列

29、功能。1 问题分析 用例分析 类图描述向队列中装入整数从队列中取出整数用户qurue-q2.*:int-head:int-tail:int+qput(in i:int)+qget():int本讲稿第四十七页,共一百四十三页 2 详细设计 类设计 qurue 类 类定义 class queue int q100;/队列空间 int head,tail;/队列头、尾指示 public:void qput(int i);/队列插入操作 int qget();/队列取出操作;本讲稿第四十八页,共一百四十三页算法描述 成员函数 qput 的 N-S 流程图:成员函数 qget 的 N-S 流程图:tai

30、l=100&tail-head=100?真 假 显示队列满 trail=trail+1,数据插入队尾 head=tail?真 假 显示队列空 head=head+1 从队头取数据 tail=100?真 假移动队列数据,修改队列指针tail=tail head;head=0;本讲稿第四十九页,共一百四十三页 类对象创建和使用main 函数的 N-S 流程图:使用 queue 类创建实例 a,b向队列实例 a,b 中分别插入:10,20 和 20,19从队列实例 a,b 中分别顺序取出数据,并显示本讲稿第五十页,共一百四十三页3.4.1 参数化的构造函数参数化的构造函数 与其他成员函数一样,构造函

31、数也可以有参数。通过这些参数为类对象的数据成员传递初值。例如:class 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 构造函数的重载构造函数的重载在一个类

32、中允许定义多个参数不同构造函数,即构造函数重载。这样就为在不同情况下创建对象的特定初始化需要提供了实现手段。也就是说,在类对象定义时,编译器可以依据创建对象所需要的参数差异确定调用构造函数的哪一个版本来创建类对象。例3-1-13-1-1 定义一个有两个构造函数的类,并使用不同构造函数定义对象。本讲稿第五十三页,共一百四十三页3.4.3 使用缺省参数值的构造函数使用缺省参数值的构造函数 与其他函数一样,构造函数的参数也可以具有缺省值,表示类对象的某些属性在大多数情况下是预先可以确定的缺省状态,例如计数器的初值一般为“0”、战士的性别多数为“男”、大学教师的学位一般为“硕士”等。构造函数的缺省参数

33、值的定义和使用规则与其他带缺省参数值的函数相同。例例3-1-23-1-2 描述了如何声明,定义和使用带有缺省参数值的类构造函数。本讲稿第五十四页,共一百四十三页归纳构造函数使用缺省参数值的编程要点是:指定缺省参数值只能在构造函数的声明中,而不能出现在构造函数定义的首部。构造函数定义在类定义体中的情况除外。函数声明中的参数可以省略参数名,此时指定缺省参数值的格式为:类型名=缺省值。例如:Box(int=10,int=10,int=10);如果构造函数的全部参数都指定了缺省值,应该避免再定义一个无参数的构造函数。因为在定义构造函数时,编译器会认为可能是重复定义。例如:Box();Box(int=1

34、0,int=10,int=10);本讲稿第五十五页,共一百四十三页更重要的是定义类对象时,遇到如下情况:Box box1;编译器无法确定调用哪一个构造函数版本来创建类对象。如果构造函数的全部参数都指定了缺省值,就容易在重载构造函数时造成二义性。例如:Box(int=10,int=10,int=10);Box();Box(int,int);因此,应避免全部参数都指定了缺省值。例如:Box();Box(int,int=10,int=10);Box(int,int);本讲稿第五十六页,共一百四十三页3.4.4 用参数初始化表对数据成员初始化用参数初始化表对数据成员初始化 所谓参数初始化是指系统在为类

35、对象的各个数据成员分配内存空间的同时能按照用户通过参数指定的值为数据成员赋值,而不是在各个数据成员的内存空间分配完成后,再对它们进行赋值。这就需要通过一种语法格式,即参数初始化表,使编译器能按照上述要求实现对类对象的各个数据成员的初始化。构造函数参数初始化表的一般形式为::基类初始化列表,属性初始化列表本讲稿第五十七页,共一百四十三页其中基类初始化列表只有在派生类的构造函数初始化表才会存在,这一部分将在第五章中介绍。属性初始化列表由若干个属性初始化项组成,项间用“,”隔开:属性初始化项1,属性初始化项2,属性初始化项n每个属性初始化项的一般格式为:属性名(参数列表)不难看出,属性初始化项的含义

36、是调用相应的属性类的具有参数的构造函数用于属性对象的创建和赋初值操作。本讲稿第五十八页,共一百四十三页这从另一个角度告诉我们,一个构造函数的定义中没有出现初始化表意味着使用了隐含的初始化表,该表的功能是分别调用了相应类的无参数(或有缺省参数值)构造函数完成类对象的基类部分和各个属性对象创建和赋初值。例3-1-33-1-3 是将例3-1-1中的类构造函数改写为使用参数初始化表实现类对象各数据成员的初始化。虽然两个实例中对类对象的数据成员初始化的结果是完全相同的,但两种初始化方法对数据成员的赋值的时间和方法是完全不同的。本讲稿第五十九页,共一百四十三页在构造函数定义中使用初始化表另一个非常重要的原

37、因就是对于类对象的常数据成员、引用数据成员的初始化就必须在创建的同时进行赋值操作,而不能在函数体中进行赋值。例如:class A public:A(int i);const int&ref;/常引用数据成员private:const int a;/常数据成员;A:A(int i):a(i),ref(a)本讲稿第六十页,共一百四十三页3.4.5 拷贝构造函数拷贝构造函数 拷贝构造函数是一个特定的构造函数。该构造函数与其他构造函数在形式上的差别仅在于函数的参数必须是同类型对象的常引用。拷贝构造函数的原型格式如下:类型名(const 类型名&引用名);拷贝构造函数的功能是创建一个新对象,并将参数所引

38、用对象的各个数据成员值复制到新对象的对应的数据成员域中。本讲稿第六十一页,共一百四十三页 系统会为每个类缺省定义一个拷贝构造函数,也允许用户定义一个拷贝构造函数,用以取代缺省的拷贝构造函数。一般情况下,使用系统定义的缺省拷贝构造函数就可以满足类对象的复制操作,但在有些情况下,用户必须定义自己的拷贝构造函数。例如:class string int length;char*str;/指针数据成员public:string(int len);本讲稿第六十二页,共一百四十三页string:string(int len)length=len;str=new charlen+1;/指针数据成员指向动态分配

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

40、ng 对象。因此,在这种情况下必须定义自己的拷贝构造函数:lengthstrs1lengthstrs2本讲稿第六十四页,共一百四十三页string:string(const string&s)length=s.length;str=new charlength+1;strcpy(str,s.str);Java 没有 C+那种含义的指针,也没有拷贝构造函数。同时,对象的撤消是由垃圾收集器完成的,因此也不会产生像 C+中那样的问题。当然在 Java 中也可以用赋值运算符“=”来进行对象赋值,但是,这并不意味着一个简单赋值操作所具有的直觉含义。例如:本讲稿第六十五页,共一百四十三页class Use

41、rpublic string name;class Test public static void main(String args)User u1=new User(“ariel”,112);System.out.println(u1.name);/arielUser u2=u1;u2.name=“muriel”;System.out.println(u1.name);/muriel本讲稿第六十六页,共一百四十三页 显然,这里的 u2=u1 只是对对象引用的复制。由于 u1 和 u2 引用同一个 User 对象,所以才会导致修改 u2.name 实际上等价于 对 u1.name 的修改(这与

42、 C+中两个指针指向同一个对象的情况类似)。如果要完成直觉意义上的复制,就必须通过实现 User 类的克隆接口 Cloneable 后,调用逐字节复制的克隆函数 clone()完成。例如:本讲稿第六十七页,共一百四十三页class User implements Cloneable public string name;class Test public static void main(String args)User u1=new User(“ariel”,112);System.out.println(u1.name);/arielUser u2=(User)u1.clone();u2.

43、name=“muriel”;System.out.println(u1.name);/ariel 本讲稿第六十八页,共一百四十三页3.5 析构函数析构函数对象撤消时,也需要一种操作,使被撤消的对象从程序的数据区中合法消失。实现这一操作的成员函数称为析构函数,该函数要完成的操作包括:回收被撤消对象数据成员所占用的内存;根据需要完成回收被撤消对象数据成员所占内存之 前的必要操作。析构函数也是类必须拥有的特殊成员函数,该函数从定义形式到使用场合和方法上都与构造函数相似,主要特点表现在以下几个方面:本讲稿第六十九页,共一百四十三页 析构函数名必须是类名加字符“”前缀,否则编译器将会把它当作一般成员函数

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

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

46、 length=0;contents=0;/设置指针数据成员为空 本讲稿第七十三页,共一百四十三页 string:string()/定义析构函数 if(contents)delete contents;/释放 contents 指向的内存空间 在程序运行过程中,类对象是在退出其作用域时才 被析构的。也就是说,此时类对象的析构函数被调 用。析构函数在类对象撤消时由系统自动执行,不需要 用户调用,也不能由用户直接调用。本讲稿第七十四页,共一百四十三页 不能为析构函数定义函数指针,也不能获取析构函 数的调用地址。基类的析构函数不能被派生类继承。析构函数可以声明为虚函数,并且提倡声明为虚函 数(详细原

47、因在第六章 运行多态性中讲述)。返回本讲稿第七十五页,共一百四十三页3.6 对象的动态创建和释放对象的动态创建和释放 与预定义类型一样,自定义类型也可以使用运算符new 动态创建对象和使用运算符 delete 撤消类对象。例例3-23-2 描述了动态创建、撤消和使用 point 类对象。注意:1 使用无参数或具有缺省参数值的构造函数动态创建类对象(即创建对象时不传递初始值)的格式为:new 类型名;例如,point*p=new point;而不应写成:new 类型名();例如,point*p=new point();上述格式与动态创建系统预定义类型变量的格式完全一致(预定义类型无缺省初始值)。

48、例如:int*p=new int;本讲稿第七十六页,共一百四十三页2 使用有参数的构造函数动态创建类对象(即创建对象时传递初始值)的格式为:new 类型名(初始值);例如,point*p=new point(10,20);上述格式与动态创建系统预定义类型变量并传递初始值的格式完全一致。例如:int*p=new int(10);本讲稿第七十七页,共一百四十三页例例3-33-3 通过构造函数对对象数组进行初始化。对象数组初始化的方法一般有两种:1 使用缺省构造函数创建对象数组后,调用一个专门用于初始化的成员函数对数组中的每个对象分别进行初始化。该方法虽然必须分两步完成对象数组的创建和初始化,但可以

49、将对象数组中的元素初始化为任意值。2 定义一个带缺省值参数的构造函数。使得在创建对象数组的同时,由构造函数的缺省参数值完成数组中的每个对象对象的初始化。使用该方法创建和初始化对象数组简单、方便,但只能将对象数组中的每个元素初始化为固定的缺省值。返回本讲稿第七十八页,共一百四十三页3.7 对象的赋值与复制对象的赋值与复制1 对象的赋值对象的赋值只能发生在同类型对象之间的,这与系统预定义类型变量的赋值是一致的。对象赋值的一般格式为:对象名1=对象名2;赋值操作是由赋值运算符“=”完成的,该运算符(函数)的功能是将对象名2 所指示对象的各个数据成员值依次传递给对象名1 所指示对象的各个数据成员,使对

50、象1 与对象2 完全相同。本讲稿第七十九页,共一百四十三页系统会为每一个自定义类型自动添加一个隐含的缺省赋值运算符,因此一般的自定义类型不必显式定义赋值运算符。但如果类定义中包含有指针类属性(3.4.5 拷贝构造函数中已经讨论这种情况),则必须显式定义赋值运算符用于取代隐含的缺省赋值运算符。如何定义赋值运算符将在第五章中讨论。例例3-2-13-2-1 描述了使用缺省赋值运算符完成对象的赋值操作。本讲稿第八十页,共一百四十三页2 对象的复制对象的复制是指按照一个已经存在的对象创建一个与该对象完全相同的新对象。显然对象的复制操作是由类的拷贝构造函数完成的,复制一个已有对象的一般形式为:类型名 对象

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 大学资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁