第 4章 类和对象第 4章 类和对象
4.1 面向对象的思想
4.2 面向对象程序设计的基本特点
4.3 面向对象的方法
4.4 面向对象的标记
4.5 类和对象
4.6 构造函数和析构函数
4.7 类的组合
4.8 类模板第 4章 类和对象
4.1 面向对象的思想
C++是一种面向对象的程序设计语言,使用它可以实现面向对象的程序设计 。 在介绍其面向对象的特性之前,我们先来了解一下面向对象程序设计的特点,
以及它与传统的结构化程序设计的联系与本质差别 。
第 4章 类和对象
4.1.1 结构化程序设计使用早期的计算机语言编写较大的程序时,由于当时未采用结构化的程序设计方法,使得程序的阅读,
理解和调试都非常困难,对程序进行维护或增加新的功能几乎是一件不可能的事情 。
第 4章 类和对象
20世纪 60 年代以来,提出了结构化程序设计的概念,它的产生和发展形成了现代软件工程的基础 。 结构化程序设计建议采用有含义的变量名,实现程序的全局和局部范围以及一种面向过程的自顶向下编程方法,它的基本思想是:自顶向下,逐步求精,将整个程序结构划分成若干个功能相对独立的子模块,并要求这些子模块间的关系尽可能简单;子模块又可继续划分,直至最简;每一个模块最终都可用顺序,选择,
循环三种基本结构来实现 。
第 4章 类和对象结构化程序设计可以有效地将一个复杂的问题分解为若干个易于处理的子问题,每一个子问题都可以独立地编程解决,从而将整个程序划分成多个子模块或过程,因此,结构化程序设计是一种面向过程的程序设计方法 。 在结构化程序设计方法中,程序的基本构成单位是函数或者过程 。
第 4章 类和对象结构化程序设计方法有许多优点:各模块可以分别编写,使得程序更易于阅读,理解,测试和修改;方便增加新的功能模块;功能独立的模块可以组成子程序库,有利于实现软件的复用 。 结构化程序设计的方法出现之后,立即为广大的程序设计人员所接受并广泛使用,成为程序设计的主流方法 。
第 4章 类和对象由于结构化程序设计方法是面向过程的,以解决问题的过程作为程序的基础和重点,因此,在方法上存在着不足。在结构化程序设计中,把程序定义为
“数据结构 +算法”,数据与处理这些数据的过程是分离的。这样,对不同格式的数据作相同的处理,或是对相同的数据作不同的处理,都需要不同的程序模块来实现,使得程序的可复用性并不好。同时,由于过程和数据相分离,数据可能被多个模块所使用和修改,
很难保证数据的安全性和一致性。
第 4章 类和对象
4.1.2 面向对象程序设计面向对象的程序设计技术是完成程序设计任务的一种新方法,它汲取了结构化程序设计中最为精华的部分 。,面向对象程序设计是被结构化的结构化程序设计 。 它是软件开发的第二次变革,是程序结构的统一理论,。
在面向对象程序设计中,将对象作为构成软件系统的基本单元,并从相同类型的对象中抽象出一种新型的数据结构 —— 类。
第 4章 类和对象对象是类的实例 。 类是一种区别于其它各种一般数据类型的特殊类型 。 类的成员中不仅包含有描述类对象属性的数据,还包含对这些数据进行处理的程序代码,称之为对象的行为 ( 或操作 ) 。 对象将其属性和行为封装在一起,并将其内部大部分的实现细节隐藏起来,仅通过一个可控的接口与外界交互 。
面向对象程序设计不仅实现了数据抽象,而且通过抽象出相关类的共性,而形成一般类 ( 基类 ),并在此基础上,采用继承的方式,对一般类增添不同的特性而派生出多种特殊类 ( 派生类 ),从而建立了类与类之间的多层结构关系,为软件复用提供了有效的途径 。
第 4章 类和对象面向对象程序设计支持多态性 。 多态性与继承性相结合,使不同结构的对象可以以各自不同的方式响应同一消息 。
软件系统的对象之间存在着依存关系,对象之间通过消息联系 。 面向对象程序设计中,消息表现为对象在起操作过程中对另一个对象的服务程序的调用 。
第 4章 类和对象
4.2 面向对象程序设计的基本特点
4.2.1 抽象性抽象是指从具体的实例中抽取出共同的性质并加以描述的过程 。 比起面向过程的程序设计,面向对象程序设计更加强调抽象性 。 在面向对象方法中,抽象是通过对一个系统进行分析和认识,强调系统中某些本质的特性,而对系统进行的简化描述 。
第 4章 类和对象一般,对问题的抽象包括两个方面:数据抽象和行为抽象 。 数据抽象为程序员提供了对对象的属性和状态的描述,行为抽象则是对这些数据所需要的操作的抽象 。
抽象的过程是通过模块化来实现的,即通过分析将一个复杂的系统分解为若干个模块,每个模块是对整个系统结构的某一部分的一个自包含的和完整的描述 。 同时,对模块中的细节部分进行信息隐藏,用户只能通过一个受保护的接口来访问模块中的数据 。 这个接口由一些操作组成,定义了该模块的行为 。
第 4章 类和对象看一个简单的例子 。 假设我们需要在计算机上实现一个绘制圆形的程序 。 通过对这个图形的分析,可以看出需要三个数据来描述该圆的位置和大小,即圆心的横,纵坐标以及圆的半径,这就是对该圆形的数据抽象 。 另外,该图形应该具有设置圆心坐标,设置半径大小,绘制圆形等功能,这就是对它的行为抽象 。
用 C++语言可以将该图形描述如下:
第 4章 类和对象圆形 (circle):
数据抽象:
double x,y,r;
行为抽象:
setx( ); sety( ); setr( ); draw( );
抽象是面向对象方法的核心 。
第 4章 类和对象
4.2.2 封装性封装是面向对象方法重要的原则 。 所谓封装,就是将一个事物包装起来,使外界不了解它的详细内情 。
在面向对象方法中,把某些相关的代码和数据结合在一起,形成一个数据和操作的封装体,这个封装体向外提供一个可以控制的接口,其内部大部分的实现细节则对外隐藏,从而达到对数据访问权限的合理控制 。 封装可以使得程序中各部分之间的相互影响达到最小,并且提高程序的安全性,简化代码的编写工作 。
第 4章 类和对象对象是面向对象程序语言中支持并实现封装的机制 。 对象中既包含有数据 ( 即属性 ),又包含有对这些数据进行处理的操作代码 ( 即行为 ),它们都称为对象的成员 。 对象中的成员可以定义成公有的或私有的 。 私有成员即在对象中被隐藏的部分,不能被该对象以外的程序访问;公有成员则提供对象与外界的接口,外界只能通过这个接口与对象发生联系 。 可以看到,对象有效实现了封装的两个目标 ——对数据和行为的包装和信息隐藏 。
第 4章 类和对象
4.2.3 继承性继承是软件复用的一种方式,通过继承,一个对象可以获得另一个对象的属性,并加入属于自己的一些特性 。 继承提供了创建新类的一种方法,即从现有类创建新类 。 新类继承了现有类的属性和行为,并通过对这些属性和行为进行扩充和修改,增添自己特有的一些性质 。
第 4章 类和对象继承简化了人们对系统的认识和描述 。 我们可以通过对一些有内在联系的类进行分析,抽象出这些类中所包含的共性,从而形成一般类的概念 。 在一般类的基础上,增添每个具体的类所具有的特性,就形成了各个不同的特殊类 。 特殊类的对象拥有一般类的全部属性和操作,我们称为特殊类对一般类的继承 。 在特殊类中,我们不必考虑继承来的属性和行为,只需着重研究它所特有的那些性质就可以了 。 这就好像在现实世界中,我们已知房子是建筑物这一概念的继承,
则房子这一概念具有建筑物的所有特点,还同时包含有它自身所特有的一些属性 。
第 4章 类和对象一个一般类可以派生出多个特殊类,不同的特殊类在一般类的基础上增加了不同的特性 。 一个类也可以继承多个一般类的特性,这称之为多继承 。
继承是很重要的概念 。 继承支持多层分类的概念,
使得一个个原来彼此孤立的类有效地组织起来,形成层次结构关系 。 倘若不使用多层分类的概念,对每个对象的清晰描述都要穷尽其特征,而采用继承的概念描述一个对象,只需在一般类特征的基础上加上该对象的一些专有特性即可 。
第 4章 类和对象
4.2.4 多态性多态性也是面向对象程序设计的重要特性之一 。
简单地说,多态性就是一个接口,多种方式 。 在基类中定义的属性和操作被派生类继承之后,可能具有不同的数据类型或表现出不同的行为,我们称之为多态性 。 也就是说,多态性表现为同一属性或操作在一般类及各特殊类中具有不同的语义 。 从同一基类派生出来的各个对象具有同一接口,因而能响应同一格式的信息,但不同类型的对象对该信息响应的方式不同,
导致产生完全不同的行为 。 在这里,消息一般是指对类的成员函数的调用,而不同的行为即用不同的函数实现 。
第 4章 类和对象很明显,实现多态性的好处在于,为这类对象提供服务时,不必区分具体是哪种对象,只需发送相同的消息即可,而由各个对象去以适合自身的方式进行不同的响应 。
举一个简单的例子 。 编制绘图程序时,不同的图形其绘制的方式是不同的 。 我们可以声明一个基类,几何图形,,该类中定义一个,绘图,行为,并定义该类的派生类,直线,,,椭圆,,,多边形,等,这些类都继承了基类中的,绘图,行为 。 在基类的,绘图,行为中,由于图形类型尚未确定,所以并不明确定义如何绘制一个图形的方法,而是在各派生类中,根据具体需要对,绘图,重新定义 。 这样,当对不同对象发出同一,绘图,命令时,
各对象调用自己的,绘图,程序实现,绘制出不同的图形 。
第 4章 类和对象
4.3 面向对象的方法前面我们已经给出了面向对象程序设计的基本思想及其特性,本节将对面向对象的方法做一个更深入的探讨 。
要了解面向对象的概念,首先要知道什么是对象 。
对象在现实世界中是一个实体或者一种事物的概念 。
现实世界中的任何一个系统都是由若干具体的对象构成 。 作为系统的一个组成部分,对象为其所在的系统提供一定的功能,担当一定的角色 。 所以,对象可以看作是一种具有自身属性和功能的构件 。
第 4章 类和对象我们在使用一个对象时,并不关心其内部结构及实现方法,仅仅关心它的功能和它在系统中的使用方法,也就是该对象提供给用户的接口 。 举个例子,对电视机这个对象来说,我们并不关心电视机的内部结构或其实现原理是怎样的,只关心如何通过按钮来使用它 。 这些按钮就是电视机提供给用户的接口 。 至于电视机内部结构原理,对用户来说是隐藏的 。 分析一个系统,也就是分析系统由哪些对象构成,以及这些对象间的相互关系 。
第 4章 类和对象我们知道,软件开发的目的是为了进行数据处理,
因此,在程序中包含有数据和与之相关的代码 。 这些数据和对它进行操作的代码是密切相关,不可分离的,
没有数据的代码和没有代码的数据同样是没有意义的 。
在面向对象方法中,我们采用与现实世界相一致的方式,将对象定义为一组数据及其相关代码的结合体,其中数据描述了对象的属性,对数据进行处理的操作则描述了对象的功能,而软件系统由多个这样的对象构成 。 对象将其属性和操作的一部分对外界开放,
作为它的对外接口,而将大部分的实现细节隐藏,这就是对象的封装性 。 外界只能使用上述定义的接口与对象交互 。
第 4章 类和对象面向对象的方法中进一步引入了类的概念 。 所谓类,就是同样类型对象的抽象描述 。 对象是类的实例 。
类是面向对象方法的核心 。 对相关的类进行分析,抽取这些类的共同特性,形成基类的概念 。 通过继承,
派生类可以包含基类的所有属性和操作,还可以增加属于自己的一些特性 。 通过继承,可以将原来一个个孤立的类联系起来,形成清晰的层次结构关系,称为类簇 。
第 4章 类和对象一个系统由多个对象组成 。 其中复杂对象可以由简单对象组合而成,称之为聚合 。 对象之间存在着依存关系,一个对象可以向另一个对象发送消息,也可以从其它对象接收消息,对象之间通过消息彼此联系,
共同协作 。 对象以及对象之间的这种相互作用构成了软件系统的结构 。
综上所述,面向对象的方法就是利用抽象、封装等机制,借助于对象、类、继承、消息传递等概念进行软件系统构造的软件开发方法。
第 4章 类和对象
4.4 面向对象的标记在面向对象程序设计中,我们可以使用面向对象标记图,将系统的构成更加直观地表述出来 。 面向对象标记图应该能够准确清楚地描述以下四个问题:类,
对象,类及对象的关系,类及对象之间的联系 。
面向对象的标记方法有很多种,其中 UML
( Unified Modeling Language,统一建模语言)是目前国际上确定的标准标记方法。它是一种比较完整的支持可视化建模的工具,但其比较复杂,我们在这里不做介绍。
第 4章 类和对象本节我们介绍一种比较简单和直观的标记方法 ——
Cord/Yourdon标记。 Cord/Yourdon标记无法对类和对象的成员的访问控制权限进行有效地描述,但这种标记方法图形简单,易于理解,而且可以清晰地表示出类和对象的相互关系和联系。
Cord/Yourdon标记中有两类图形符号:表示符号和连接符号 。 表示符号用来表示类和对象 。
Cord/Yourdon标记中用一个圆角矩形来表示类。矩形内部分为三个部分,上部是类名,中部表示该类的数据成员,下部则表示该类的成员函数。
第 4章 类和对象图 4-1给出了类的标记方法和一个 point类的标记实例 。 point类将在本章的后面部分定义和使用 。
图 4-1 类的标记图类名数据成员成员函数
point
int x,y;
point(int,int);
point(point);
int getx(?);
int gety(?);
第 4章 类和对象对象是类的实例 。 在 Cord/Yourdon标记中,对象是在相应类标记外加一个圆角矩形框,如图 4-2所示 。 p1
是 point类的一个对象,表示屏幕上的一个点 。
图 4-2 对象的标记图对象名数据成员成员函数
p1
int x,y;
point(int,int);
point(point);
int getx(?);
int gety(?);
第 4章 类和对象连接符号主要有三种,它们分别表示消息联系,
继承关系和包含关系,如图 4-3所示 。
图 4-3 Cord/Yourdon标记中的连接符号第 4章 类和对象图 4-3 Cord/Yourdon标记中的连接符号第 4章 类和对象下面给出了一个采用 Cord/Yourdon标记描述的关系图,描述了两个类 —— distance类与 point类之间的关系。
distance类表示屏幕上两个点的距离,该类中包含有两个 point类的对象 p1和 p2。从标记图 4-4中可以清楚的看出这两个类之间的组合关系,有关的具体内容将在后续章节介绍。
第 4章 类和对象图 4-4 distance类与 point类的关系图
p1
int x,y;
point(int,int);
point(point
&);
int getx(?);
int gety(?);
p2
int x,y;
point(int,int);
point(point
&);
int getx(?);
int gety(?);
distance
point p1,p2;
double dist;
distance(point,poi
nt)
double getdis(?);
第 4章 类和对象
4.5 类 和 对 象类是面向对象程序设计的基础和核心,也是实现数据抽象的工具 。 在面向过程的结构化程序设计中,
组成程序的基本构件是函数 。 函数由若干相关语句构成,完成对某些数据的处理 。 由于这些函数和数据之间是相对独立的,使程序修改起来比较麻烦,而且难以保证数据的可靠性和一致性 。 而在面向对象的程序设计中,将彼此相关的数据和与这些数据相关的操作
( 函数 ) 封装在一起,形成类这样一种新的具有更高的集成性和抽象性的数据类型 。
第 4章 类和对象类实际上相当于一种特殊的用户自定义的数据类型,但它和一般的数据类型有着不同之处:类中不仅包含有一组相关的数据,还包含能对这些数据进行处理的函数 。 类中的数据具有隐藏性和封装性,类是实现 C++的许多高级特性的基础 。
我们可以声明属于某个类的变量,这种变量称之为类的对象 。 在 C++中,类和对象的关系实际上是数据类型和具体变量的关系 。 在程序中可以通过类定义中提供的函数访问该类对象的数据 。
第 4章 类和对象
4.5.1 类的声明类的声明即类的定义 。 声明一个类的语法与结构的声明类似,其一般形式如下:
class <类名 >
{
private:
<私有成员函数和数据成员的说明 >
public:
<公有成员函数和数据成员的说明 >
};
<各个成员函数的实现 >
第 4章 类和对象其中,class是声明类的关键字; <类名 >是标识符,
表示声明的类的名字;类声明体内的函数和变量称为这个类的成员,分别称为成员函数和数据成员; public、
private等关键字用来说明类的成员的访问控制属性 。
类的成员函数用于对数据成员处理,又称为,方法,。 程序中通过类的成员函数来访问其内部的数据成员 。 成员函数是类与外部程序之间的接口 。 在类的声明体中只需声明成员函数的原型,成员函数的具体实现可放在类外定义 。
第 4章 类和对象通常采用下面的形式定义成员函数:
<类型标识符 > <类名 >,,<成员函数名 >( <形参表 >)
{
<函数体 >
}
下面的例子就是一个简单的类的说明 。
第 4章 类和对象
【 例 4-1】 类的声明例题 。
class myclass
{
private:
int a;
public:
void set_a(int num);
int get_a( );
};
void myclass::set_a(int num)
第 4章 类和对象
{
a=num;
}
int myclass::get_a( )
{
return a;
}
第 4章 类和对象
4.5.2 类成员的访问控制在缺省情况下,类中说明的所有函数和变量都为这个类所私有,也就是说,这些函数和变量只能在该类成员函数的实现中被访问 。 为了说明某些成员是公有成员 ( 可以被其它类的成员访问的成员称为公有成员 ),必须使用关键字 public,并在后面加上冒号 。 这样,限定词 public之后的所有变量和函数不但可以被同一个类的其它成员访问,还可以被程序中该类以外的对象访问 。
第 4章 类和对象一般,C++中定义类时,可将类的各个成员划分成不同的访问级别 。 这可以通过在所说明的成员前面加上访问控制属性来实现 。 C++规定有三种访问控制属性,public表示成员是公有的,private表示成员是私有的,protected则表示成员是受保护的 。
第 4章 类和对象公有成员可以由程序中的任何函数访问,而私有成员只允许本类的成员函数访问,任何外部程序对它进行访问都是非法的 。 可以看到,私有成员是在类中被隐藏的部分,它往往是用来描述该类对象属性的一些数据成员,这些数据成员用户无法访问,只有通过成员函数或某些特殊说明的函数才可引用;而公有成员一般是成员函数,它提供了外部程序与类的接口功能,用户通过公有成员访问该类对象中的数据 。
第 4章 类和对象保护成员与私有成员在一般情况下含义相同,它们的区别体现在类的继承中对产生的新类的影响不同 。
有关保护成员的具体内容将在第 7章详细介绍 。
上节所定义的类 myclass中的数据成员 a是一个私有变量,故 myclass以外的任何代码都不能访问它;
set_a( )和 get_a( )是 myclass的成员函数,因此它们可以访问 a;另外,由于 set_a( )和 get_a( )被说明为 myclass的公有成员,所以允许类以外的其它程序代码调用它们 。
第 4章 类和对象
4.5.3 类的成员函数类中成员函数的原型声明写在类定义体内,用以说明该成员函数的形式参数和返回值类型,而成员函数的定义体一般写在类定义之外 。 同一般函数一样,
类的成员函数可以重载,也可以带有缺省的形参值 。
例 4-1中,若将成员函数 set_a( )作如下定义:
void myclass::set_a(int num = 10)
{
a=num;
}
第 4章 类和对象这样,如果在调用这个函数时没有给出实参,程序会把定义中默认的缺省形参值 10赋给对象的私有成员 a。
在函数中有内联函数的概念。我们可以将那些仅由少数几条简单代码组成,却在程序中被频繁调用的函数定义为内联函数。程序在编译时,将内联函数的函数体插入到每一次调用它的地方。这样,在程序运行时就省去了调用这些函数引起的开销,提高了程序的执行效率。
第 4章 类和对象我们同样可以将类中的成员函数定义为内联函数,
为此可采用如下两种方式:
① 将成员函数的函数体直接放在类声明中 。
② 使用 inline关键字来限定,即在类定义体中说明函数原型时前面加上 inline,或者在类定义体外定义这个函数时前面加上 inline。
是否将一个成员函数定义为内联函数是由这个函数的复杂性决定的。
第 4章 类和对象
【 例 4-2】 类的成员函数的定义例题 。
class rectangle
{
private:
int x,y,weight,high;
public:
void move(int xx,int yy) {x = xx; y = yy;}
void size(int w,int h) {weight = w; high = h;}
void where(int& xx,int& yy) {xx = x; yy= y;}
int area( );
第 4章 类和对象
};
int rectangle::area( )
{
return weight * high;
}
第 4章 类和对象
4.5.4 对象前面我们声明了一个类 myclass,但并没有创建它的任何对象,仅仅是定义了即将创建的对象的类型 。
要真正创建该类型的对象,必须进行对象说明 。 说明一个对象的方法与说明一般变量的方法相同 。 下面的例子定义了 myclass类的两个对象:
myclass ob1,ob2;
类和对象的关系相当于普通数据类型与其变量的关系。
第 4章 类和对象类是一种逻辑抽象概念 。 声明一个类只是定义了一种新的数据类型,对象说明才真正创建了这种数据类型的物理实体 。 由同一个类创建的各个对象具有完全相同的数据结构,但它们的数据值可能不同 。
一旦创建了一个类的对象,程序就可以用运算符
,.”来引用类的公有成员,其引用形式如下:
<对象名 >,<公有数据成员名 >
或
<对象名 >,<公有成员函数名 ( 实参表 ) >
第 4章 类和对象假设已经说明了对象 ob1,ob2和整型变量 value,
下面的语句完成对 ob1和 ob2成员函数 set_a( )与 get_a( )
的调用:
ob1.set_a(20);
ob2.set_a(99);
value = ob2.get_a( );
cout << ob1.get_a( );
第 4章 类和对象需要注意,只有用 public定义的公有成员才能使用圆点操作符访问 。 对象中的私有成员是类中隐藏的数据,不允许在类外的程序中被直接访问,只能通过该类的公有成员函数来访问它们 。 请判断下面的语句哪些是错误的:
ob1.a = 10; //错误 ! a是私有成员
ob2.a = ob1.a; //错误 ! a是私有成员
ob2.set_a(ob1.get_a( )); //正确 ! set_a( )和 get_a( )都是公有成员另外,C++中允许同属一个类的两个对象之间相互赋值。
第 4章 类和对象
ob1 = ob2;
会将对象 ob2的所有数据成员的值完全复制到对象
ob1的相应成员中去 。
但是在有些情况下,对象间的相互赋值存在着潜在的危险 。 比如,类中包含有指针类型的数据成员时,
对象的整体赋值会使两个不同对象中的指针成员指向同一内存空间,使用时须非常小心 。
第 4章 类和对象
4.5.5 对象数组数组的元素可以是基本类型的数据,也可以是用户自定义类型的数据,对象数组就是指数组元素为对象的数组 。 对象数组中的各个元素必须是属于同一个类的若干对象 。
对象数组的定义和使用与一般数组类似 。 不同的是,对象数组的每个元素都是对象,其中不仅包含数据成员,还包含有成员函数 。 所以,对象数组的使用有其特殊之处 。
第 4章 类和对象声明对象数组的一般形式如下:
<类名 > <数组名 >[下标表达式 ] …
其中,<类名 >指出该对象数组元素所属的类; [<下标表达式 >] 给出数组的维数和大小 。 例如,声明语句
myclass obs[10];
定义了一个一维的对象数组 obs,该数组有 10个元素,每个元素都是 myclass类的对象 。
又例如:
myclass obs2[3][5];
第 4章 类和对象表明 obs2是一个二维对象数组,包含 15个属于
myclass类的对象 。
声明了对象数组之后,就可以引用其数组元素 。
该数组元素是一个对象,故只能访问其公有成员 。 引用数组元素的一般形式如下:
<数组名 >[下标 ],<公有成员名 >
例如:
obs[6],set_a(60);
cout << obs[3],get_a(60);
对象数组允许被赋初值,也允许被赋值。
第 4章 类和对象
【 例 4-3】 对象数组例题 。
#include <iostream.h>
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d );
void print( );
};
第 4章 类和对象
date::date(int y=2000,int m=1,int d=1 )
{
year = y;
month = m;
day = d;
}
void date::print( )
{
cout << year << "."<< month << "."<< day << endl;
}
void main( )
第 4章 类和对象
{
date
days[4]={date(2001,8,11),date(2001,8,12),date(2001,8,13),date(2001,8,
14)};
for (int i=0; i<4; i++)
days[i].print( );
}
程序运行结果为
2001.8.11
2001.8.12
2001.8.13
2001.8.14
第 4章 类和对象
4.6 构造函数和析构函数
4.6.1 构造函数构造函数是在类中声明的一种特殊的成员函数,
作用是在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的状态 。
构造函数的名字与它所属的类名相同,被声明为公有函数,且没有任何类型的返回值,在创建对象时被自动调用 。
第 4章 类和对象构造函数作为类的一个成员函数,具有一般成员函数所有的特性,它可以访问类的所有数据成员,可以是内联函数,可以带有参数表,还可以带默认的形参值 。 构造函数也可以重载,以提供初始化类对象的不同方法 。
下面的例子说明了构造函数的定义和使用。
第 4章 类和对象
【 例 4-4】 构造函数例题 。
#include <iostream.h>
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d );
void print( );
};
第 4章 类和对象
date::date(int y,int m,int d )
{
year = y;
month = m;
day = d;
cout << "constructor called" << endl;
}
void date::print( )
{
cout << year << "."<< month << "."<< day << endl;
}
第 4章 类和对象
void main( )
{
date today(2001,8,11),tomorrow(2001,8,12);
cout << "today is ";
today.print( );
cout << "tomorrow is ";
tomorrow.print( );
}
第 4章 类和对象程序运行结果为
constructor called
constructor called
today is 2001.8.11
tomorrow is 2001.8.12
说明:本程序在 date类中定义了一个带参数的构造函数。从运行结果可以看出,当主程序中声明两个 date
类的对象 today和 tomorrow时,构造函数被自动调用两次。由于该构造函数带有形参,故在对象声明时必须通过实参给出初始值。
第 4章 类和对象每个类都必须有构造函数,若类定义时没有定义任何构造函数,编译器会自动生成一个不带参数的缺省构造函数,其形式如下:
<类名 >,,<缺省构造函数名 > ( )
{
//...
}
缺省构造函数名与类名相同 。
第 4章 类和对象
4.6.2 拷贝构造函数我们常常希望用一个已有对象来构造同一类型的另一对象,这可以通过调用一种特殊的构造函数 ——拷贝构造函数来实现 。
拷贝构造函数是重载构造函数的一种重要形式,它的功能是使用一个已经存在的对象去初始化一个新创建的同类的对象,它可以将一个已有对象的数据成员的值拷贝给正在创建的另一个同类的对象 。
第 4章 类和对象例如,已经有了一个 date类的对象 today,然后希望生成一个和它一样的对象 workday,我们可用以下形式实现:
date today(2001,8,11);
date workday(today);
在创建对象 workday时,系统调用拷贝构造函数,
将对象 today的每个数据成员的值都复制到 workday中,
使两者具有同样的值 。
第 4章 类和对象拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,其名字也与所属类名相同 。 拷贝构造函数中只有一个参数,这个参数是对某个同类对象的引用 。
定义拷贝构造函数的一般形式如下:
class class_name
{
private:
…
public:
class_name(形式参数表 );
第 4章 类和对象
class_name(class_name &ob_name);
…
}
class_name::class_name(class_name &ob_name);
{
函数体
}
第 4章 类和对象例如,对前面定义的 date类可以增添拷贝构造函数如下:
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d ); //构造函数
date( date &day); //拷贝构造函数
void print( );
}
…
第 4章 类和对象
date::date(date &myday)
{
year = myday.year;
month = myday.month;
day = myday.day;
}
…
第 4章 类和对象拷贝构造函数在三种情况下会被调用:
① 用类的一个对象去初始化该类的另一个对象时 。
② 函数的形参是类的对象,调用函数进行形参和实参的结合时 。
③ 函数的返回值是类的对象,函数执行完返回调用者时 。
第 4章 类和对象
【 例 4-5】 拷贝构造函数的使用例题 。
#include <iostream.h>
class point
{
private:
int x,y;
public:
point(int xx = 0,int yy = 0) {x = xx; y = yy;}
point(point &p);
第 4章 类和对象
int get_x( ) {return x;}
int get_y( ) {return y;}
};
point::point(point &p)
{
x = p.x;
y = p.y;
cout << "拷贝构造函数被调用 "<< endl;
}
void f ( point p)
{
第 4章 类和对象
cout << p.get_x( ) << " " << p.get_y( ) << endl;
}
point g ( )
{
point a(7,33);
return a;
}
void main ( )
{
point a(15,22);
point b( a );
第 4章 类和对象
cout << b.get_x( ) << " " << b.get_y( ) << endl;
f (b);
b = g ( );
cout << b.get_x( ) << " " << b.get_y( ) << endl;
}
程序的运行结果为拷贝构造函数被调用
15 22
拷贝构造函数被调用
15 22
拷贝构造函数被调用
7 33
第 4章 类和对象说明:此程序中定义了一个 point类,表示屏幕上一个点。类中两个私有成员 x和 y,分别为该点的横、
纵坐标,类中分别定义了构造函数和拷贝构造函数。
主程序中,当声明对象 a时,系统调用了构造函数初始化其值,而当声明对象 b时,则调用拷贝构造函数,将其值初始化为同 a一样的值,属于拷贝构造函数调用的第一种情况。
第 4章 类和对象主程序中调用函数 f( 其形参是一个 point类的对象 ),进行形参与实参的结合,这时系统再次调用拷贝构造函数,将对象 b的值拷贝到形参 p中,这是拷贝构造函数调用的第二种情况 。 函数 g的返回值是一个
point类的对象,系统为该返回值创建一个临时对象,
并调用拷贝构造函数将局部对象 a的值拷贝至其中,函数 g运行结束后,将此临时对象的值赋与对象 b,这是拷贝构造函数调用的第三种情况 。
第 4章 类和对象最后需要说明的是,每一个类都必须有一个拷贝构造函数,但不是都必须要自己定义 。 如果我们在类中没有定义,则系统会自动帮我们定义一个默认的拷贝构造函数,该函数自动完成将一个对象的所有数据成员复制到另一个对象中的所有操作 。 比如,如果我们没有为上述的 date类定义拷贝构造函数,系统会自动创建一个拷贝构造函数,其功能与我们定义的一样 。
第 4章 类和对象
4.6.3 析构函数一个对象失效时,要调用该对象所属类的析构函数 。 析构函数的功能是用来释放一个对象的 。 析构函数本身并不实际删除对象,而是进行系统放弃对象内存之前的清理工作,使内存可用来保存新的数据 。 它与构造函数的功能正好相反 。
析构函数也是类的成员函数,它的名字是在类名前加字符“~”。析构函数没有参数,也没有返回值。
析构函数不能重载,也就是说,一个类中只可能定义一个析构函数。
第 4章 类和对象析构函数可以在程序中被调用,也可由系统自动调用 。 在函数体内定义的对象,当函数执行结束时,
该对象所在类的析构函数会被自动调用 。 用 new运算符动态创建的对象,在使用 delete运算符释放它时,也会自动调用其析构函数 。
同样,如果一个类中没有定义析构函数,系统也会为它自动生成一个默认的析构函数 。 该析构函数是一个空函数,什么都不做 。
第 4章 类和对象
【 例 4-6】 构造函数和析构函数例题 。
#include <iostream.h>
class myclass
{
private:
int a;
public:
myclass( ) {cout << "constructor" << endl; a = 10;}
~myclass( ) { cout << "destructor" << endl;}
第 4章 类和对象
void show( ) { cout << a << endl;}
};
void main( )
{
myclass ob;
ob.show( );
}
程序的运行结果为
constructor
10
destructor
第 4章 类和对象析构函数通常用于对象退出生命期时,释放这个对象所占用的一些资源。例如,某个对象在运行程序过程中申请了一些内存空间,则需要在对象结束生命期前将这些空间释放;或者在打开了某个文件或数据库时,需要在对象退出生命期前关闭这些文件或数据库。
第 4章 类和对象
4.7 类 的 组 合在现实世界中,我们解决复杂的问题时,通常采用将其层层分解为简单问题的方法 。 即可将一个复杂的问题分解为几个较简单的子问题描述出来,而这些子问题又可以进一步分解,由更简单的子问题来描述 。
这样,只要这些最基本,最简单的子问题得以描述和解决,由它们构成的复杂问题就迎刃而解了 。
第 4章 类和对象同样的思想可应用于面向对象的程序设计方法中 。
确定一个对象的内部结构可能是很困难的一件事,但我们可以通过将复杂对象层层分解为若干简单的,部件,对象的组合,然后再由这些易于描述和实现的部件对象来,装配,复杂对象 。
C++中允许将一个已定义的类的对象作为另一个类的数据成员,这称之为类的组合。即类可以将其它类对象作为自己的成员,形成一种包含和被包含的关系。
例如:
第 4章 类和对象
Class A
{
private:
…
public:
…
};
Class B
{
private:
A a;
第 4章 类和对象
…
public:
…
};
其中,B类中的数据成员 a就是一个 A类的对象,称之为对象成员 。
在定义一个类时,其数据成员既可以是简单类型,
又可以是自定义类型,还可以是类的对象 。 这样,我们定义类时,就可以利用已定义的类来构成新类,由若干结构简单,易于实现的类来构造复杂的类 。 这种类似于部件组装的方法,不仅简化了问题的描述,而且有利于提高软件的开发效率,也是软件复用的一种形式 。
第 4章 类和对象对组合类,当创建该类的对象时,其中包含的各个对象成员也将被自动创建 。 故该类的构造函数应包含对其中对象成员的初始化 。 通常采用成员初始化列表的方法来初始化对象成员 。 在成员初始化列表中,
既包含对对象成员的初始化,又包含对本类中其它的基本数据成员的初始化 。 下面通过一个例子简单说明成员初始化列表的构造 。
第 4章 类和对象
【 例 4-7】 类的组合例题 。
#include <iostream.h>
class A
{
private:
int a1,a2;
public:
A(int i,int j) {a1=i,a2=j;}
void print( ) {cout<<a1<<","<<a2<<endl;}
};
class B
第 4章 类和对象
{
private:
A a;
int b;
public:
B(int i,int j,int k),a(i,j),b(k) {}
void print( );
};
void B::print( )
{
a.print( );
cout << b << endl;
}
第 4章 类和对象
void main( )
{
B b(3,4,5);
b.print( );
}
在该程序中,B类中包含一个 A类的对象 a,它是一个对象成员 。 该程序执行后的输出结果为
3,4
5
请注意,B类的构造函数构成如下:
B(int i,int j,int k),a(i,j),b(k)
{ }
第 4章 类和对象其中,构造函数冒号后的部分 a(i,j),b(k)被称为成员初始化列表,该表列出了为初始化对象成员所使用的构造函数。当建立 B类的对象时,对象成员 a首先被建立。为构造该对象成员,所指定的构造函数(即 A类对象的构造函数)被执行。 B类对象的一般数据成员 b
也可用此方式初始化其值为 k。
第 4章 类和对象事实上,当建立一个组合类对象时,它所包含的所有对象成员也一同被建立 。 当所有的对象成员被构造完毕之后 ( 即它们所在类的构造函数被执行完 ),
该对象的类的构造函数体才被执行 。 析构函数的执行顺序与构造函数刚好相反 。
另外要注意,各个成员对象的构造函数的调用次序与这些对象成员在类中的声明次序一致,而与成员初始化列表中给出的构造函数的次序无关。
第 4章 类和对象综上所述,下面给出组合类构造函数定义的一般形式:
<类名 >:,<类名 >( 形参表 ),对象成员 1( 形参表 ),
对象成员 2( 形参表 ),…
{类的初始化程序体 }
其中,构造函数冒号后的部分:,对象成员 1( 形参表 ),对象成员 2( 形参表 ),…”称作成员初始化列表,用于完成对组合类中所包含的对象成员的初始化 。
该表列出了初始化各对象成员所使用的构造函数 。 当创建该组合类对象时,这些构造函数按在类中声明的次序依次被调用,将类中对象成员依次建立 。 最后,执行该类的构造函数体 。 至此,该组合类对象创建完毕 。
第 4章 类和对象
【 例 4-8】 构造函数调用次序例题 。
#include <iostream.h>
#include <math.h>
class point
{
private:
int x,y;
public:
point(int i=0,int j=0) {x=i; y=j;}
point(point &p);
int get_x( ) {return x;}
第 4章 类和对象
int get_y( ) {return y;}
};
point::point(point &p)
{
x = p.x;
y = p.y;
cout<<"point拷贝构造函数被调用 "<<endl;
}
class distance
{
private:
第 4章 类和对象
point p1,p2;
double dist;
public:
distance(point xp1,point xp2);
double get_dist( ) {return dist;}
};
distance::distance(point xp1,point xp2),p1(xp1),p2(xp2)
{
cout<<"distance构造函数被调用 "<<endl;
double x = double(p1.get_x( ) - p2.get_x( ));
double y = double(p1.get_y( )- p2.get_y( ));
第 4章 类和对象
dist = sqrt(x*x + y*y );
}
void main( )
{
point myp1(1,1),myp2(4,5);
distance myd(myp1,myp2);
cout<<"the distance is:";
cout<<myd.get_dist( )<<endl;
}
第 4章 类和对象程序运行结果为
point拷贝构造函数被调用
point拷贝构造函数被调用
point拷贝构造函数被调用
point拷贝构造函数被调用
distance构造函数被调用
the distance is:5
说明:本例中定义了两个类。 point类表示点;
distance类表示两点间的距离,该类中包含两个 point类的对象成员 p1和 p2,因此是一个组合类。
第 4章 类和对象
distance类在其构造函数中初始化对象成员 p1和 p2,
并计算这两点间的距离且存放在私有数据成员 dist中,
其值可通过该类的公有成员函数 get_dist( )得到 。 在主程序中,当声明 distance类的对象 myd时,其包含的对象成员 p1和 p2首先被建立 。 从程序运行结果可以看出,
distance类的构造函数体被执行之前,point类的拷贝构造函数被调用 4次,分别是两个 point类对象 myp1和
myp2在 distance类的构造函数进行函数参数形实结合和初始化对象成员时调用的 。
第 4章 类和对象
4.8 类 模 板模板是 C++支持参数化多态性的工具 。 所谓参数化多态性,就是将一段程序所处理的对象的类型参数化,
使得这段程序可以用于处理多种不同类型的对象,从而实现了代码复用 ——C++最重要的特性之一 。
由于 C++程序结构的主要构件是类和函数,所以,
模板在 C++中有类模板和函数模板两种 。 在第 2章中我们已经介绍了函数模板,本节将介绍类模板 。
第 4章 类和对象类模板使我们在声明一个类时,能够将实现这个类所需要的某些数据类型(包括类中数据成员的类型、
成员函数的参数的类型或其返回值的类型)参数化,
使之成为一个可以处理多种类型数据的通用类。而在创建类对象时,通过指定参数所代表的实际数据类型,
将通用类实例化。所建立的实例类是通用类的一个副本,但是它具有指定的类型。
第 4章 类和对象当一个类表示像数组、链表、矩阵等这类数据结构或包含有通用的逻辑算法时,类模板变得非常有用。因为这些数据结构的表示和算法的选择不受其所包含的元素的数据类型的影响。例如,维护一个整数队列的算法同样适用于维护字符队列。因此,可以通过定义一个类模板,创建一个可以维护队列的类,队列中的元素可以是任意数据类型。在声明该类的具体对象时,编译器会根据指定的数据类型自动产生该类的实例。
第 4章 类和对象声明类模板的一般形式如下:
template <class Ttype>
class class_name
{
…
}
其中,Ttype是一个标识符,代表所声明的类模板中参数化的类型名。当实例化该通用类时,将由一个具体的类型代替它。若有多个参数化的类型名,可将它们依次罗列,用逗号隔开。
第 4章 类和对象可以看到,类模板的声明与通常的类定义没什么不同,只是以如下的首部开头:
template <模板参数表 >
上述首部表明,这是一个类模板的定义 。 模板参数表由参数化的形式类型名组成,可以包含下列内容 。
- class 标识符其中,标识符即为形式类型名,代表参数化的类型名 。
- 类型说明符 标识符该标识符可以接受一个常量作为参数,常量的类型由,类型说明符,指定 。
第 4章 类和对象注意,模板类的成员函数必须是函数模板 。
一旦定义了类模板,就可以用如下的语句创建这个类的实例:
class_name <type> 对象 1,…,对象 n;
type为一个具体的数据类型名,与类模板声明中的形式类型名相对应,系统根据这个实际的数据类型生成所需的类,并创建该类的对象 。
下面通过一个简单的例子说明类模板的使用。
第 4章 类和对象
【 例 4-9】 类模板的使用例题 。
#include <iostream.h>
#include <stdlib.h>
struct student
{
int id;
int score;
};
template <class T>
class buffer
{
第 4章 类和对象
private:
T a;
int empty;
public:
buffer(void);
T get(void);
void put(T x);
};
template <class T>
buffer <T>::buffer(void):empty(0) {}
template <class T>
T buffer <T>::get(void)
第 4章 类和对象
{
if ( empty == 0 )
{
cout << "the buffer is empty!"<< endl;
exit(1);
}
return a;
}
template <class T>
void buffer <T>,,put(T x)
{
empty++;
a = x;
第 4章 类和对象
}
void main(void)
{
student s = {1022,78};
buffer <int> i1,i2;
buffer <student> stu1;
buffer <double> d;
i1.put(13);
i2.put(-101);
cout << i1.get( ) << " "<< i2.get( ) << endl;
stu1.put(s);
cout << "the student's id is "<< stu1.get( ).id << endl;
第 4章 类和对象
cout << "the student's score is "<< stu1.get( ).score << endl;
cout << d.get( ) << endl;
}
程序运行结果为
13 -101
the student's id is 1022
the student's score is 78
the buffer is empty!
4.1 面向对象的思想
4.2 面向对象程序设计的基本特点
4.3 面向对象的方法
4.4 面向对象的标记
4.5 类和对象
4.6 构造函数和析构函数
4.7 类的组合
4.8 类模板第 4章 类和对象
4.1 面向对象的思想
C++是一种面向对象的程序设计语言,使用它可以实现面向对象的程序设计 。 在介绍其面向对象的特性之前,我们先来了解一下面向对象程序设计的特点,
以及它与传统的结构化程序设计的联系与本质差别 。
第 4章 类和对象
4.1.1 结构化程序设计使用早期的计算机语言编写较大的程序时,由于当时未采用结构化的程序设计方法,使得程序的阅读,
理解和调试都非常困难,对程序进行维护或增加新的功能几乎是一件不可能的事情 。
第 4章 类和对象
20世纪 60 年代以来,提出了结构化程序设计的概念,它的产生和发展形成了现代软件工程的基础 。 结构化程序设计建议采用有含义的变量名,实现程序的全局和局部范围以及一种面向过程的自顶向下编程方法,它的基本思想是:自顶向下,逐步求精,将整个程序结构划分成若干个功能相对独立的子模块,并要求这些子模块间的关系尽可能简单;子模块又可继续划分,直至最简;每一个模块最终都可用顺序,选择,
循环三种基本结构来实现 。
第 4章 类和对象结构化程序设计可以有效地将一个复杂的问题分解为若干个易于处理的子问题,每一个子问题都可以独立地编程解决,从而将整个程序划分成多个子模块或过程,因此,结构化程序设计是一种面向过程的程序设计方法 。 在结构化程序设计方法中,程序的基本构成单位是函数或者过程 。
第 4章 类和对象结构化程序设计方法有许多优点:各模块可以分别编写,使得程序更易于阅读,理解,测试和修改;方便增加新的功能模块;功能独立的模块可以组成子程序库,有利于实现软件的复用 。 结构化程序设计的方法出现之后,立即为广大的程序设计人员所接受并广泛使用,成为程序设计的主流方法 。
第 4章 类和对象由于结构化程序设计方法是面向过程的,以解决问题的过程作为程序的基础和重点,因此,在方法上存在着不足。在结构化程序设计中,把程序定义为
“数据结构 +算法”,数据与处理这些数据的过程是分离的。这样,对不同格式的数据作相同的处理,或是对相同的数据作不同的处理,都需要不同的程序模块来实现,使得程序的可复用性并不好。同时,由于过程和数据相分离,数据可能被多个模块所使用和修改,
很难保证数据的安全性和一致性。
第 4章 类和对象
4.1.2 面向对象程序设计面向对象的程序设计技术是完成程序设计任务的一种新方法,它汲取了结构化程序设计中最为精华的部分 。,面向对象程序设计是被结构化的结构化程序设计 。 它是软件开发的第二次变革,是程序结构的统一理论,。
在面向对象程序设计中,将对象作为构成软件系统的基本单元,并从相同类型的对象中抽象出一种新型的数据结构 —— 类。
第 4章 类和对象对象是类的实例 。 类是一种区别于其它各种一般数据类型的特殊类型 。 类的成员中不仅包含有描述类对象属性的数据,还包含对这些数据进行处理的程序代码,称之为对象的行为 ( 或操作 ) 。 对象将其属性和行为封装在一起,并将其内部大部分的实现细节隐藏起来,仅通过一个可控的接口与外界交互 。
面向对象程序设计不仅实现了数据抽象,而且通过抽象出相关类的共性,而形成一般类 ( 基类 ),并在此基础上,采用继承的方式,对一般类增添不同的特性而派生出多种特殊类 ( 派生类 ),从而建立了类与类之间的多层结构关系,为软件复用提供了有效的途径 。
第 4章 类和对象面向对象程序设计支持多态性 。 多态性与继承性相结合,使不同结构的对象可以以各自不同的方式响应同一消息 。
软件系统的对象之间存在着依存关系,对象之间通过消息联系 。 面向对象程序设计中,消息表现为对象在起操作过程中对另一个对象的服务程序的调用 。
第 4章 类和对象
4.2 面向对象程序设计的基本特点
4.2.1 抽象性抽象是指从具体的实例中抽取出共同的性质并加以描述的过程 。 比起面向过程的程序设计,面向对象程序设计更加强调抽象性 。 在面向对象方法中,抽象是通过对一个系统进行分析和认识,强调系统中某些本质的特性,而对系统进行的简化描述 。
第 4章 类和对象一般,对问题的抽象包括两个方面:数据抽象和行为抽象 。 数据抽象为程序员提供了对对象的属性和状态的描述,行为抽象则是对这些数据所需要的操作的抽象 。
抽象的过程是通过模块化来实现的,即通过分析将一个复杂的系统分解为若干个模块,每个模块是对整个系统结构的某一部分的一个自包含的和完整的描述 。 同时,对模块中的细节部分进行信息隐藏,用户只能通过一个受保护的接口来访问模块中的数据 。 这个接口由一些操作组成,定义了该模块的行为 。
第 4章 类和对象看一个简单的例子 。 假设我们需要在计算机上实现一个绘制圆形的程序 。 通过对这个图形的分析,可以看出需要三个数据来描述该圆的位置和大小,即圆心的横,纵坐标以及圆的半径,这就是对该圆形的数据抽象 。 另外,该图形应该具有设置圆心坐标,设置半径大小,绘制圆形等功能,这就是对它的行为抽象 。
用 C++语言可以将该图形描述如下:
第 4章 类和对象圆形 (circle):
数据抽象:
double x,y,r;
行为抽象:
setx( ); sety( ); setr( ); draw( );
抽象是面向对象方法的核心 。
第 4章 类和对象
4.2.2 封装性封装是面向对象方法重要的原则 。 所谓封装,就是将一个事物包装起来,使外界不了解它的详细内情 。
在面向对象方法中,把某些相关的代码和数据结合在一起,形成一个数据和操作的封装体,这个封装体向外提供一个可以控制的接口,其内部大部分的实现细节则对外隐藏,从而达到对数据访问权限的合理控制 。 封装可以使得程序中各部分之间的相互影响达到最小,并且提高程序的安全性,简化代码的编写工作 。
第 4章 类和对象对象是面向对象程序语言中支持并实现封装的机制 。 对象中既包含有数据 ( 即属性 ),又包含有对这些数据进行处理的操作代码 ( 即行为 ),它们都称为对象的成员 。 对象中的成员可以定义成公有的或私有的 。 私有成员即在对象中被隐藏的部分,不能被该对象以外的程序访问;公有成员则提供对象与外界的接口,外界只能通过这个接口与对象发生联系 。 可以看到,对象有效实现了封装的两个目标 ——对数据和行为的包装和信息隐藏 。
第 4章 类和对象
4.2.3 继承性继承是软件复用的一种方式,通过继承,一个对象可以获得另一个对象的属性,并加入属于自己的一些特性 。 继承提供了创建新类的一种方法,即从现有类创建新类 。 新类继承了现有类的属性和行为,并通过对这些属性和行为进行扩充和修改,增添自己特有的一些性质 。
第 4章 类和对象继承简化了人们对系统的认识和描述 。 我们可以通过对一些有内在联系的类进行分析,抽象出这些类中所包含的共性,从而形成一般类的概念 。 在一般类的基础上,增添每个具体的类所具有的特性,就形成了各个不同的特殊类 。 特殊类的对象拥有一般类的全部属性和操作,我们称为特殊类对一般类的继承 。 在特殊类中,我们不必考虑继承来的属性和行为,只需着重研究它所特有的那些性质就可以了 。 这就好像在现实世界中,我们已知房子是建筑物这一概念的继承,
则房子这一概念具有建筑物的所有特点,还同时包含有它自身所特有的一些属性 。
第 4章 类和对象一个一般类可以派生出多个特殊类,不同的特殊类在一般类的基础上增加了不同的特性 。 一个类也可以继承多个一般类的特性,这称之为多继承 。
继承是很重要的概念 。 继承支持多层分类的概念,
使得一个个原来彼此孤立的类有效地组织起来,形成层次结构关系 。 倘若不使用多层分类的概念,对每个对象的清晰描述都要穷尽其特征,而采用继承的概念描述一个对象,只需在一般类特征的基础上加上该对象的一些专有特性即可 。
第 4章 类和对象
4.2.4 多态性多态性也是面向对象程序设计的重要特性之一 。
简单地说,多态性就是一个接口,多种方式 。 在基类中定义的属性和操作被派生类继承之后,可能具有不同的数据类型或表现出不同的行为,我们称之为多态性 。 也就是说,多态性表现为同一属性或操作在一般类及各特殊类中具有不同的语义 。 从同一基类派生出来的各个对象具有同一接口,因而能响应同一格式的信息,但不同类型的对象对该信息响应的方式不同,
导致产生完全不同的行为 。 在这里,消息一般是指对类的成员函数的调用,而不同的行为即用不同的函数实现 。
第 4章 类和对象很明显,实现多态性的好处在于,为这类对象提供服务时,不必区分具体是哪种对象,只需发送相同的消息即可,而由各个对象去以适合自身的方式进行不同的响应 。
举一个简单的例子 。 编制绘图程序时,不同的图形其绘制的方式是不同的 。 我们可以声明一个基类,几何图形,,该类中定义一个,绘图,行为,并定义该类的派生类,直线,,,椭圆,,,多边形,等,这些类都继承了基类中的,绘图,行为 。 在基类的,绘图,行为中,由于图形类型尚未确定,所以并不明确定义如何绘制一个图形的方法,而是在各派生类中,根据具体需要对,绘图,重新定义 。 这样,当对不同对象发出同一,绘图,命令时,
各对象调用自己的,绘图,程序实现,绘制出不同的图形 。
第 4章 类和对象
4.3 面向对象的方法前面我们已经给出了面向对象程序设计的基本思想及其特性,本节将对面向对象的方法做一个更深入的探讨 。
要了解面向对象的概念,首先要知道什么是对象 。
对象在现实世界中是一个实体或者一种事物的概念 。
现实世界中的任何一个系统都是由若干具体的对象构成 。 作为系统的一个组成部分,对象为其所在的系统提供一定的功能,担当一定的角色 。 所以,对象可以看作是一种具有自身属性和功能的构件 。
第 4章 类和对象我们在使用一个对象时,并不关心其内部结构及实现方法,仅仅关心它的功能和它在系统中的使用方法,也就是该对象提供给用户的接口 。 举个例子,对电视机这个对象来说,我们并不关心电视机的内部结构或其实现原理是怎样的,只关心如何通过按钮来使用它 。 这些按钮就是电视机提供给用户的接口 。 至于电视机内部结构原理,对用户来说是隐藏的 。 分析一个系统,也就是分析系统由哪些对象构成,以及这些对象间的相互关系 。
第 4章 类和对象我们知道,软件开发的目的是为了进行数据处理,
因此,在程序中包含有数据和与之相关的代码 。 这些数据和对它进行操作的代码是密切相关,不可分离的,
没有数据的代码和没有代码的数据同样是没有意义的 。
在面向对象方法中,我们采用与现实世界相一致的方式,将对象定义为一组数据及其相关代码的结合体,其中数据描述了对象的属性,对数据进行处理的操作则描述了对象的功能,而软件系统由多个这样的对象构成 。 对象将其属性和操作的一部分对外界开放,
作为它的对外接口,而将大部分的实现细节隐藏,这就是对象的封装性 。 外界只能使用上述定义的接口与对象交互 。
第 4章 类和对象面向对象的方法中进一步引入了类的概念 。 所谓类,就是同样类型对象的抽象描述 。 对象是类的实例 。
类是面向对象方法的核心 。 对相关的类进行分析,抽取这些类的共同特性,形成基类的概念 。 通过继承,
派生类可以包含基类的所有属性和操作,还可以增加属于自己的一些特性 。 通过继承,可以将原来一个个孤立的类联系起来,形成清晰的层次结构关系,称为类簇 。
第 4章 类和对象一个系统由多个对象组成 。 其中复杂对象可以由简单对象组合而成,称之为聚合 。 对象之间存在着依存关系,一个对象可以向另一个对象发送消息,也可以从其它对象接收消息,对象之间通过消息彼此联系,
共同协作 。 对象以及对象之间的这种相互作用构成了软件系统的结构 。
综上所述,面向对象的方法就是利用抽象、封装等机制,借助于对象、类、继承、消息传递等概念进行软件系统构造的软件开发方法。
第 4章 类和对象
4.4 面向对象的标记在面向对象程序设计中,我们可以使用面向对象标记图,将系统的构成更加直观地表述出来 。 面向对象标记图应该能够准确清楚地描述以下四个问题:类,
对象,类及对象的关系,类及对象之间的联系 。
面向对象的标记方法有很多种,其中 UML
( Unified Modeling Language,统一建模语言)是目前国际上确定的标准标记方法。它是一种比较完整的支持可视化建模的工具,但其比较复杂,我们在这里不做介绍。
第 4章 类和对象本节我们介绍一种比较简单和直观的标记方法 ——
Cord/Yourdon标记。 Cord/Yourdon标记无法对类和对象的成员的访问控制权限进行有效地描述,但这种标记方法图形简单,易于理解,而且可以清晰地表示出类和对象的相互关系和联系。
Cord/Yourdon标记中有两类图形符号:表示符号和连接符号 。 表示符号用来表示类和对象 。
Cord/Yourdon标记中用一个圆角矩形来表示类。矩形内部分为三个部分,上部是类名,中部表示该类的数据成员,下部则表示该类的成员函数。
第 4章 类和对象图 4-1给出了类的标记方法和一个 point类的标记实例 。 point类将在本章的后面部分定义和使用 。
图 4-1 类的标记图类名数据成员成员函数
point
int x,y;
point(int,int);
point(point);
int getx(?);
int gety(?);
第 4章 类和对象对象是类的实例 。 在 Cord/Yourdon标记中,对象是在相应类标记外加一个圆角矩形框,如图 4-2所示 。 p1
是 point类的一个对象,表示屏幕上的一个点 。
图 4-2 对象的标记图对象名数据成员成员函数
p1
int x,y;
point(int,int);
point(point);
int getx(?);
int gety(?);
第 4章 类和对象连接符号主要有三种,它们分别表示消息联系,
继承关系和包含关系,如图 4-3所示 。
图 4-3 Cord/Yourdon标记中的连接符号第 4章 类和对象图 4-3 Cord/Yourdon标记中的连接符号第 4章 类和对象下面给出了一个采用 Cord/Yourdon标记描述的关系图,描述了两个类 —— distance类与 point类之间的关系。
distance类表示屏幕上两个点的距离,该类中包含有两个 point类的对象 p1和 p2。从标记图 4-4中可以清楚的看出这两个类之间的组合关系,有关的具体内容将在后续章节介绍。
第 4章 类和对象图 4-4 distance类与 point类的关系图
p1
int x,y;
point(int,int);
point(point
&);
int getx(?);
int gety(?);
p2
int x,y;
point(int,int);
point(point
&);
int getx(?);
int gety(?);
distance
point p1,p2;
double dist;
distance(point,poi
nt)
double getdis(?);
第 4章 类和对象
4.5 类 和 对 象类是面向对象程序设计的基础和核心,也是实现数据抽象的工具 。 在面向过程的结构化程序设计中,
组成程序的基本构件是函数 。 函数由若干相关语句构成,完成对某些数据的处理 。 由于这些函数和数据之间是相对独立的,使程序修改起来比较麻烦,而且难以保证数据的可靠性和一致性 。 而在面向对象的程序设计中,将彼此相关的数据和与这些数据相关的操作
( 函数 ) 封装在一起,形成类这样一种新的具有更高的集成性和抽象性的数据类型 。
第 4章 类和对象类实际上相当于一种特殊的用户自定义的数据类型,但它和一般的数据类型有着不同之处:类中不仅包含有一组相关的数据,还包含能对这些数据进行处理的函数 。 类中的数据具有隐藏性和封装性,类是实现 C++的许多高级特性的基础 。
我们可以声明属于某个类的变量,这种变量称之为类的对象 。 在 C++中,类和对象的关系实际上是数据类型和具体变量的关系 。 在程序中可以通过类定义中提供的函数访问该类对象的数据 。
第 4章 类和对象
4.5.1 类的声明类的声明即类的定义 。 声明一个类的语法与结构的声明类似,其一般形式如下:
class <类名 >
{
private:
<私有成员函数和数据成员的说明 >
public:
<公有成员函数和数据成员的说明 >
};
<各个成员函数的实现 >
第 4章 类和对象其中,class是声明类的关键字; <类名 >是标识符,
表示声明的类的名字;类声明体内的函数和变量称为这个类的成员,分别称为成员函数和数据成员; public、
private等关键字用来说明类的成员的访问控制属性 。
类的成员函数用于对数据成员处理,又称为,方法,。 程序中通过类的成员函数来访问其内部的数据成员 。 成员函数是类与外部程序之间的接口 。 在类的声明体中只需声明成员函数的原型,成员函数的具体实现可放在类外定义 。
第 4章 类和对象通常采用下面的形式定义成员函数:
<类型标识符 > <类名 >,,<成员函数名 >( <形参表 >)
{
<函数体 >
}
下面的例子就是一个简单的类的说明 。
第 4章 类和对象
【 例 4-1】 类的声明例题 。
class myclass
{
private:
int a;
public:
void set_a(int num);
int get_a( );
};
void myclass::set_a(int num)
第 4章 类和对象
{
a=num;
}
int myclass::get_a( )
{
return a;
}
第 4章 类和对象
4.5.2 类成员的访问控制在缺省情况下,类中说明的所有函数和变量都为这个类所私有,也就是说,这些函数和变量只能在该类成员函数的实现中被访问 。 为了说明某些成员是公有成员 ( 可以被其它类的成员访问的成员称为公有成员 ),必须使用关键字 public,并在后面加上冒号 。 这样,限定词 public之后的所有变量和函数不但可以被同一个类的其它成员访问,还可以被程序中该类以外的对象访问 。
第 4章 类和对象一般,C++中定义类时,可将类的各个成员划分成不同的访问级别 。 这可以通过在所说明的成员前面加上访问控制属性来实现 。 C++规定有三种访问控制属性,public表示成员是公有的,private表示成员是私有的,protected则表示成员是受保护的 。
第 4章 类和对象公有成员可以由程序中的任何函数访问,而私有成员只允许本类的成员函数访问,任何外部程序对它进行访问都是非法的 。 可以看到,私有成员是在类中被隐藏的部分,它往往是用来描述该类对象属性的一些数据成员,这些数据成员用户无法访问,只有通过成员函数或某些特殊说明的函数才可引用;而公有成员一般是成员函数,它提供了外部程序与类的接口功能,用户通过公有成员访问该类对象中的数据 。
第 4章 类和对象保护成员与私有成员在一般情况下含义相同,它们的区别体现在类的继承中对产生的新类的影响不同 。
有关保护成员的具体内容将在第 7章详细介绍 。
上节所定义的类 myclass中的数据成员 a是一个私有变量,故 myclass以外的任何代码都不能访问它;
set_a( )和 get_a( )是 myclass的成员函数,因此它们可以访问 a;另外,由于 set_a( )和 get_a( )被说明为 myclass的公有成员,所以允许类以外的其它程序代码调用它们 。
第 4章 类和对象
4.5.3 类的成员函数类中成员函数的原型声明写在类定义体内,用以说明该成员函数的形式参数和返回值类型,而成员函数的定义体一般写在类定义之外 。 同一般函数一样,
类的成员函数可以重载,也可以带有缺省的形参值 。
例 4-1中,若将成员函数 set_a( )作如下定义:
void myclass::set_a(int num = 10)
{
a=num;
}
第 4章 类和对象这样,如果在调用这个函数时没有给出实参,程序会把定义中默认的缺省形参值 10赋给对象的私有成员 a。
在函数中有内联函数的概念。我们可以将那些仅由少数几条简单代码组成,却在程序中被频繁调用的函数定义为内联函数。程序在编译时,将内联函数的函数体插入到每一次调用它的地方。这样,在程序运行时就省去了调用这些函数引起的开销,提高了程序的执行效率。
第 4章 类和对象我们同样可以将类中的成员函数定义为内联函数,
为此可采用如下两种方式:
① 将成员函数的函数体直接放在类声明中 。
② 使用 inline关键字来限定,即在类定义体中说明函数原型时前面加上 inline,或者在类定义体外定义这个函数时前面加上 inline。
是否将一个成员函数定义为内联函数是由这个函数的复杂性决定的。
第 4章 类和对象
【 例 4-2】 类的成员函数的定义例题 。
class rectangle
{
private:
int x,y,weight,high;
public:
void move(int xx,int yy) {x = xx; y = yy;}
void size(int w,int h) {weight = w; high = h;}
void where(int& xx,int& yy) {xx = x; yy= y;}
int area( );
第 4章 类和对象
};
int rectangle::area( )
{
return weight * high;
}
第 4章 类和对象
4.5.4 对象前面我们声明了一个类 myclass,但并没有创建它的任何对象,仅仅是定义了即将创建的对象的类型 。
要真正创建该类型的对象,必须进行对象说明 。 说明一个对象的方法与说明一般变量的方法相同 。 下面的例子定义了 myclass类的两个对象:
myclass ob1,ob2;
类和对象的关系相当于普通数据类型与其变量的关系。
第 4章 类和对象类是一种逻辑抽象概念 。 声明一个类只是定义了一种新的数据类型,对象说明才真正创建了这种数据类型的物理实体 。 由同一个类创建的各个对象具有完全相同的数据结构,但它们的数据值可能不同 。
一旦创建了一个类的对象,程序就可以用运算符
,.”来引用类的公有成员,其引用形式如下:
<对象名 >,<公有数据成员名 >
或
<对象名 >,<公有成员函数名 ( 实参表 ) >
第 4章 类和对象假设已经说明了对象 ob1,ob2和整型变量 value,
下面的语句完成对 ob1和 ob2成员函数 set_a( )与 get_a( )
的调用:
ob1.set_a(20);
ob2.set_a(99);
value = ob2.get_a( );
cout << ob1.get_a( );
第 4章 类和对象需要注意,只有用 public定义的公有成员才能使用圆点操作符访问 。 对象中的私有成员是类中隐藏的数据,不允许在类外的程序中被直接访问,只能通过该类的公有成员函数来访问它们 。 请判断下面的语句哪些是错误的:
ob1.a = 10; //错误 ! a是私有成员
ob2.a = ob1.a; //错误 ! a是私有成员
ob2.set_a(ob1.get_a( )); //正确 ! set_a( )和 get_a( )都是公有成员另外,C++中允许同属一个类的两个对象之间相互赋值。
第 4章 类和对象
ob1 = ob2;
会将对象 ob2的所有数据成员的值完全复制到对象
ob1的相应成员中去 。
但是在有些情况下,对象间的相互赋值存在着潜在的危险 。 比如,类中包含有指针类型的数据成员时,
对象的整体赋值会使两个不同对象中的指针成员指向同一内存空间,使用时须非常小心 。
第 4章 类和对象
4.5.5 对象数组数组的元素可以是基本类型的数据,也可以是用户自定义类型的数据,对象数组就是指数组元素为对象的数组 。 对象数组中的各个元素必须是属于同一个类的若干对象 。
对象数组的定义和使用与一般数组类似 。 不同的是,对象数组的每个元素都是对象,其中不仅包含数据成员,还包含有成员函数 。 所以,对象数组的使用有其特殊之处 。
第 4章 类和对象声明对象数组的一般形式如下:
<类名 > <数组名 >[下标表达式 ] …
其中,<类名 >指出该对象数组元素所属的类; [<下标表达式 >] 给出数组的维数和大小 。 例如,声明语句
myclass obs[10];
定义了一个一维的对象数组 obs,该数组有 10个元素,每个元素都是 myclass类的对象 。
又例如:
myclass obs2[3][5];
第 4章 类和对象表明 obs2是一个二维对象数组,包含 15个属于
myclass类的对象 。
声明了对象数组之后,就可以引用其数组元素 。
该数组元素是一个对象,故只能访问其公有成员 。 引用数组元素的一般形式如下:
<数组名 >[下标 ],<公有成员名 >
例如:
obs[6],set_a(60);
cout << obs[3],get_a(60);
对象数组允许被赋初值,也允许被赋值。
第 4章 类和对象
【 例 4-3】 对象数组例题 。
#include <iostream.h>
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d );
void print( );
};
第 4章 类和对象
date::date(int y=2000,int m=1,int d=1 )
{
year = y;
month = m;
day = d;
}
void date::print( )
{
cout << year << "."<< month << "."<< day << endl;
}
void main( )
第 4章 类和对象
{
date
days[4]={date(2001,8,11),date(2001,8,12),date(2001,8,13),date(2001,8,
14)};
for (int i=0; i<4; i++)
days[i].print( );
}
程序运行结果为
2001.8.11
2001.8.12
2001.8.13
2001.8.14
第 4章 类和对象
4.6 构造函数和析构函数
4.6.1 构造函数构造函数是在类中声明的一种特殊的成员函数,
作用是在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的状态 。
构造函数的名字与它所属的类名相同,被声明为公有函数,且没有任何类型的返回值,在创建对象时被自动调用 。
第 4章 类和对象构造函数作为类的一个成员函数,具有一般成员函数所有的特性,它可以访问类的所有数据成员,可以是内联函数,可以带有参数表,还可以带默认的形参值 。 构造函数也可以重载,以提供初始化类对象的不同方法 。
下面的例子说明了构造函数的定义和使用。
第 4章 类和对象
【 例 4-4】 构造函数例题 。
#include <iostream.h>
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d );
void print( );
};
第 4章 类和对象
date::date(int y,int m,int d )
{
year = y;
month = m;
day = d;
cout << "constructor called" << endl;
}
void date::print( )
{
cout << year << "."<< month << "."<< day << endl;
}
第 4章 类和对象
void main( )
{
date today(2001,8,11),tomorrow(2001,8,12);
cout << "today is ";
today.print( );
cout << "tomorrow is ";
tomorrow.print( );
}
第 4章 类和对象程序运行结果为
constructor called
constructor called
today is 2001.8.11
tomorrow is 2001.8.12
说明:本程序在 date类中定义了一个带参数的构造函数。从运行结果可以看出,当主程序中声明两个 date
类的对象 today和 tomorrow时,构造函数被自动调用两次。由于该构造函数带有形参,故在对象声明时必须通过实参给出初始值。
第 4章 类和对象每个类都必须有构造函数,若类定义时没有定义任何构造函数,编译器会自动生成一个不带参数的缺省构造函数,其形式如下:
<类名 >,,<缺省构造函数名 > ( )
{
//...
}
缺省构造函数名与类名相同 。
第 4章 类和对象
4.6.2 拷贝构造函数我们常常希望用一个已有对象来构造同一类型的另一对象,这可以通过调用一种特殊的构造函数 ——拷贝构造函数来实现 。
拷贝构造函数是重载构造函数的一种重要形式,它的功能是使用一个已经存在的对象去初始化一个新创建的同类的对象,它可以将一个已有对象的数据成员的值拷贝给正在创建的另一个同类的对象 。
第 4章 类和对象例如,已经有了一个 date类的对象 today,然后希望生成一个和它一样的对象 workday,我们可用以下形式实现:
date today(2001,8,11);
date workday(today);
在创建对象 workday时,系统调用拷贝构造函数,
将对象 today的每个数据成员的值都复制到 workday中,
使两者具有同样的值 。
第 4章 类和对象拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,其名字也与所属类名相同 。 拷贝构造函数中只有一个参数,这个参数是对某个同类对象的引用 。
定义拷贝构造函数的一般形式如下:
class class_name
{
private:
…
public:
class_name(形式参数表 );
第 4章 类和对象
class_name(class_name &ob_name);
…
}
class_name::class_name(class_name &ob_name);
{
函数体
}
第 4章 类和对象例如,对前面定义的 date类可以增添拷贝构造函数如下:
class date
{
private:
int year,month,day;
public:
date( int y,int m,int d ); //构造函数
date( date &day); //拷贝构造函数
void print( );
}
…
第 4章 类和对象
date::date(date &myday)
{
year = myday.year;
month = myday.month;
day = myday.day;
}
…
第 4章 类和对象拷贝构造函数在三种情况下会被调用:
① 用类的一个对象去初始化该类的另一个对象时 。
② 函数的形参是类的对象,调用函数进行形参和实参的结合时 。
③ 函数的返回值是类的对象,函数执行完返回调用者时 。
第 4章 类和对象
【 例 4-5】 拷贝构造函数的使用例题 。
#include <iostream.h>
class point
{
private:
int x,y;
public:
point(int xx = 0,int yy = 0) {x = xx; y = yy;}
point(point &p);
第 4章 类和对象
int get_x( ) {return x;}
int get_y( ) {return y;}
};
point::point(point &p)
{
x = p.x;
y = p.y;
cout << "拷贝构造函数被调用 "<< endl;
}
void f ( point p)
{
第 4章 类和对象
cout << p.get_x( ) << " " << p.get_y( ) << endl;
}
point g ( )
{
point a(7,33);
return a;
}
void main ( )
{
point a(15,22);
point b( a );
第 4章 类和对象
cout << b.get_x( ) << " " << b.get_y( ) << endl;
f (b);
b = g ( );
cout << b.get_x( ) << " " << b.get_y( ) << endl;
}
程序的运行结果为拷贝构造函数被调用
15 22
拷贝构造函数被调用
15 22
拷贝构造函数被调用
7 33
第 4章 类和对象说明:此程序中定义了一个 point类,表示屏幕上一个点。类中两个私有成员 x和 y,分别为该点的横、
纵坐标,类中分别定义了构造函数和拷贝构造函数。
主程序中,当声明对象 a时,系统调用了构造函数初始化其值,而当声明对象 b时,则调用拷贝构造函数,将其值初始化为同 a一样的值,属于拷贝构造函数调用的第一种情况。
第 4章 类和对象主程序中调用函数 f( 其形参是一个 point类的对象 ),进行形参与实参的结合,这时系统再次调用拷贝构造函数,将对象 b的值拷贝到形参 p中,这是拷贝构造函数调用的第二种情况 。 函数 g的返回值是一个
point类的对象,系统为该返回值创建一个临时对象,
并调用拷贝构造函数将局部对象 a的值拷贝至其中,函数 g运行结束后,将此临时对象的值赋与对象 b,这是拷贝构造函数调用的第三种情况 。
第 4章 类和对象最后需要说明的是,每一个类都必须有一个拷贝构造函数,但不是都必须要自己定义 。 如果我们在类中没有定义,则系统会自动帮我们定义一个默认的拷贝构造函数,该函数自动完成将一个对象的所有数据成员复制到另一个对象中的所有操作 。 比如,如果我们没有为上述的 date类定义拷贝构造函数,系统会自动创建一个拷贝构造函数,其功能与我们定义的一样 。
第 4章 类和对象
4.6.3 析构函数一个对象失效时,要调用该对象所属类的析构函数 。 析构函数的功能是用来释放一个对象的 。 析构函数本身并不实际删除对象,而是进行系统放弃对象内存之前的清理工作,使内存可用来保存新的数据 。 它与构造函数的功能正好相反 。
析构函数也是类的成员函数,它的名字是在类名前加字符“~”。析构函数没有参数,也没有返回值。
析构函数不能重载,也就是说,一个类中只可能定义一个析构函数。
第 4章 类和对象析构函数可以在程序中被调用,也可由系统自动调用 。 在函数体内定义的对象,当函数执行结束时,
该对象所在类的析构函数会被自动调用 。 用 new运算符动态创建的对象,在使用 delete运算符释放它时,也会自动调用其析构函数 。
同样,如果一个类中没有定义析构函数,系统也会为它自动生成一个默认的析构函数 。 该析构函数是一个空函数,什么都不做 。
第 4章 类和对象
【 例 4-6】 构造函数和析构函数例题 。
#include <iostream.h>
class myclass
{
private:
int a;
public:
myclass( ) {cout << "constructor" << endl; a = 10;}
~myclass( ) { cout << "destructor" << endl;}
第 4章 类和对象
void show( ) { cout << a << endl;}
};
void main( )
{
myclass ob;
ob.show( );
}
程序的运行结果为
constructor
10
destructor
第 4章 类和对象析构函数通常用于对象退出生命期时,释放这个对象所占用的一些资源。例如,某个对象在运行程序过程中申请了一些内存空间,则需要在对象结束生命期前将这些空间释放;或者在打开了某个文件或数据库时,需要在对象退出生命期前关闭这些文件或数据库。
第 4章 类和对象
4.7 类 的 组 合在现实世界中,我们解决复杂的问题时,通常采用将其层层分解为简单问题的方法 。 即可将一个复杂的问题分解为几个较简单的子问题描述出来,而这些子问题又可以进一步分解,由更简单的子问题来描述 。
这样,只要这些最基本,最简单的子问题得以描述和解决,由它们构成的复杂问题就迎刃而解了 。
第 4章 类和对象同样的思想可应用于面向对象的程序设计方法中 。
确定一个对象的内部结构可能是很困难的一件事,但我们可以通过将复杂对象层层分解为若干简单的,部件,对象的组合,然后再由这些易于描述和实现的部件对象来,装配,复杂对象 。
C++中允许将一个已定义的类的对象作为另一个类的数据成员,这称之为类的组合。即类可以将其它类对象作为自己的成员,形成一种包含和被包含的关系。
例如:
第 4章 类和对象
Class A
{
private:
…
public:
…
};
Class B
{
private:
A a;
第 4章 类和对象
…
public:
…
};
其中,B类中的数据成员 a就是一个 A类的对象,称之为对象成员 。
在定义一个类时,其数据成员既可以是简单类型,
又可以是自定义类型,还可以是类的对象 。 这样,我们定义类时,就可以利用已定义的类来构成新类,由若干结构简单,易于实现的类来构造复杂的类 。 这种类似于部件组装的方法,不仅简化了问题的描述,而且有利于提高软件的开发效率,也是软件复用的一种形式 。
第 4章 类和对象对组合类,当创建该类的对象时,其中包含的各个对象成员也将被自动创建 。 故该类的构造函数应包含对其中对象成员的初始化 。 通常采用成员初始化列表的方法来初始化对象成员 。 在成员初始化列表中,
既包含对对象成员的初始化,又包含对本类中其它的基本数据成员的初始化 。 下面通过一个例子简单说明成员初始化列表的构造 。
第 4章 类和对象
【 例 4-7】 类的组合例题 。
#include <iostream.h>
class A
{
private:
int a1,a2;
public:
A(int i,int j) {a1=i,a2=j;}
void print( ) {cout<<a1<<","<<a2<<endl;}
};
class B
第 4章 类和对象
{
private:
A a;
int b;
public:
B(int i,int j,int k),a(i,j),b(k) {}
void print( );
};
void B::print( )
{
a.print( );
cout << b << endl;
}
第 4章 类和对象
void main( )
{
B b(3,4,5);
b.print( );
}
在该程序中,B类中包含一个 A类的对象 a,它是一个对象成员 。 该程序执行后的输出结果为
3,4
5
请注意,B类的构造函数构成如下:
B(int i,int j,int k),a(i,j),b(k)
{ }
第 4章 类和对象其中,构造函数冒号后的部分 a(i,j),b(k)被称为成员初始化列表,该表列出了为初始化对象成员所使用的构造函数。当建立 B类的对象时,对象成员 a首先被建立。为构造该对象成员,所指定的构造函数(即 A类对象的构造函数)被执行。 B类对象的一般数据成员 b
也可用此方式初始化其值为 k。
第 4章 类和对象事实上,当建立一个组合类对象时,它所包含的所有对象成员也一同被建立 。 当所有的对象成员被构造完毕之后 ( 即它们所在类的构造函数被执行完 ),
该对象的类的构造函数体才被执行 。 析构函数的执行顺序与构造函数刚好相反 。
另外要注意,各个成员对象的构造函数的调用次序与这些对象成员在类中的声明次序一致,而与成员初始化列表中给出的构造函数的次序无关。
第 4章 类和对象综上所述,下面给出组合类构造函数定义的一般形式:
<类名 >:,<类名 >( 形参表 ),对象成员 1( 形参表 ),
对象成员 2( 形参表 ),…
{类的初始化程序体 }
其中,构造函数冒号后的部分:,对象成员 1( 形参表 ),对象成员 2( 形参表 ),…”称作成员初始化列表,用于完成对组合类中所包含的对象成员的初始化 。
该表列出了初始化各对象成员所使用的构造函数 。 当创建该组合类对象时,这些构造函数按在类中声明的次序依次被调用,将类中对象成员依次建立 。 最后,执行该类的构造函数体 。 至此,该组合类对象创建完毕 。
第 4章 类和对象
【 例 4-8】 构造函数调用次序例题 。
#include <iostream.h>
#include <math.h>
class point
{
private:
int x,y;
public:
point(int i=0,int j=0) {x=i; y=j;}
point(point &p);
int get_x( ) {return x;}
第 4章 类和对象
int get_y( ) {return y;}
};
point::point(point &p)
{
x = p.x;
y = p.y;
cout<<"point拷贝构造函数被调用 "<<endl;
}
class distance
{
private:
第 4章 类和对象
point p1,p2;
double dist;
public:
distance(point xp1,point xp2);
double get_dist( ) {return dist;}
};
distance::distance(point xp1,point xp2),p1(xp1),p2(xp2)
{
cout<<"distance构造函数被调用 "<<endl;
double x = double(p1.get_x( ) - p2.get_x( ));
double y = double(p1.get_y( )- p2.get_y( ));
第 4章 类和对象
dist = sqrt(x*x + y*y );
}
void main( )
{
point myp1(1,1),myp2(4,5);
distance myd(myp1,myp2);
cout<<"the distance is:";
cout<<myd.get_dist( )<<endl;
}
第 4章 类和对象程序运行结果为
point拷贝构造函数被调用
point拷贝构造函数被调用
point拷贝构造函数被调用
point拷贝构造函数被调用
distance构造函数被调用
the distance is:5
说明:本例中定义了两个类。 point类表示点;
distance类表示两点间的距离,该类中包含两个 point类的对象成员 p1和 p2,因此是一个组合类。
第 4章 类和对象
distance类在其构造函数中初始化对象成员 p1和 p2,
并计算这两点间的距离且存放在私有数据成员 dist中,
其值可通过该类的公有成员函数 get_dist( )得到 。 在主程序中,当声明 distance类的对象 myd时,其包含的对象成员 p1和 p2首先被建立 。 从程序运行结果可以看出,
distance类的构造函数体被执行之前,point类的拷贝构造函数被调用 4次,分别是两个 point类对象 myp1和
myp2在 distance类的构造函数进行函数参数形实结合和初始化对象成员时调用的 。
第 4章 类和对象
4.8 类 模 板模板是 C++支持参数化多态性的工具 。 所谓参数化多态性,就是将一段程序所处理的对象的类型参数化,
使得这段程序可以用于处理多种不同类型的对象,从而实现了代码复用 ——C++最重要的特性之一 。
由于 C++程序结构的主要构件是类和函数,所以,
模板在 C++中有类模板和函数模板两种 。 在第 2章中我们已经介绍了函数模板,本节将介绍类模板 。
第 4章 类和对象类模板使我们在声明一个类时,能够将实现这个类所需要的某些数据类型(包括类中数据成员的类型、
成员函数的参数的类型或其返回值的类型)参数化,
使之成为一个可以处理多种类型数据的通用类。而在创建类对象时,通过指定参数所代表的实际数据类型,
将通用类实例化。所建立的实例类是通用类的一个副本,但是它具有指定的类型。
第 4章 类和对象当一个类表示像数组、链表、矩阵等这类数据结构或包含有通用的逻辑算法时,类模板变得非常有用。因为这些数据结构的表示和算法的选择不受其所包含的元素的数据类型的影响。例如,维护一个整数队列的算法同样适用于维护字符队列。因此,可以通过定义一个类模板,创建一个可以维护队列的类,队列中的元素可以是任意数据类型。在声明该类的具体对象时,编译器会根据指定的数据类型自动产生该类的实例。
第 4章 类和对象声明类模板的一般形式如下:
template <class Ttype>
class class_name
{
…
}
其中,Ttype是一个标识符,代表所声明的类模板中参数化的类型名。当实例化该通用类时,将由一个具体的类型代替它。若有多个参数化的类型名,可将它们依次罗列,用逗号隔开。
第 4章 类和对象可以看到,类模板的声明与通常的类定义没什么不同,只是以如下的首部开头:
template <模板参数表 >
上述首部表明,这是一个类模板的定义 。 模板参数表由参数化的形式类型名组成,可以包含下列内容 。
- class 标识符其中,标识符即为形式类型名,代表参数化的类型名 。
- 类型说明符 标识符该标识符可以接受一个常量作为参数,常量的类型由,类型说明符,指定 。
第 4章 类和对象注意,模板类的成员函数必须是函数模板 。
一旦定义了类模板,就可以用如下的语句创建这个类的实例:
class_name <type> 对象 1,…,对象 n;
type为一个具体的数据类型名,与类模板声明中的形式类型名相对应,系统根据这个实际的数据类型生成所需的类,并创建该类的对象 。
下面通过一个简单的例子说明类模板的使用。
第 4章 类和对象
【 例 4-9】 类模板的使用例题 。
#include <iostream.h>
#include <stdlib.h>
struct student
{
int id;
int score;
};
template <class T>
class buffer
{
第 4章 类和对象
private:
T a;
int empty;
public:
buffer(void);
T get(void);
void put(T x);
};
template <class T>
buffer <T>::buffer(void):empty(0) {}
template <class T>
T buffer <T>::get(void)
第 4章 类和对象
{
if ( empty == 0 )
{
cout << "the buffer is empty!"<< endl;
exit(1);
}
return a;
}
template <class T>
void buffer <T>,,put(T x)
{
empty++;
a = x;
第 4章 类和对象
}
void main(void)
{
student s = {1022,78};
buffer <int> i1,i2;
buffer <student> stu1;
buffer <double> d;
i1.put(13);
i2.put(-101);
cout << i1.get( ) << " "<< i2.get( ) << endl;
stu1.put(s);
cout << "the student's id is "<< stu1.get( ).id << endl;
第 4章 类和对象
cout << "the student's score is "<< stu1.get( ).score << endl;
cout << d.get( ) << endl;
}
程序运行结果为
13 -101
the student's id is 1022
the student's score is 78
the buffer is empty!