2023年面向对象程序设计_第1页
2023年面向对象程序设计_第2页
2023年面向对象程序设计_第3页
2023年面向对象程序设计_第4页
2023年面向对象程序设计_第5页
已阅读5页,还剩89页未读 继续免费阅读

下载本文档

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

文档简介

第3章面向对象程序设计

3.1面向对象程序设计思想

3.1.1历史回顾

1967年挪威计算中心的KistenNygaardW0leJohanDahl开发了Simula67

语言,它提供了比子程序更高一级的抽象和封装,引入了数据抽象和类的概念,它被认为是

第一个面向对象语言。20世纪70年代初,PaloAlto研究中心的AlanKay所在的研

究小组开发出Smallta1k语言,之后又开发出Smalltalk-80,Smallta1k-80被认

为是最纯正的面向对象语言,它对后来出现的面向对象语言,如Object-C,C++,S

elf,Eiff1都产生了深远的影响。随着面向对象语言的出现,面向对象程序设计也就应运

而生且得到迅速发展。之后,面向对象不断向其他阶段渗透,1980年GradyRooch提出

了面向对象设计的概念,之后面向对象分析开始。1985年,第一个商用面向对象数据库

问世。1990年以来,面向对象分析、测试、度量和管理等研究都得到长足发展。

事实上,“对象”和“对象的属性”这样的概念可以追溯到20世纪50年代初,它们

一方面出现于关于人工智能的初期著作中。但是出现了面向对象语言之后,面向对象思想

才得到了迅速的发展。过去的几十年中,程序设计语言对抽象机制的支持限度不断提高:从

机器语言到汇编语言,到高级语言,直到面向对象语言。汇编语言出现后,程序员就避免

了直接使用0-1,而是运用符号来表达机器指令,从而更方便地编写程序;当程序规模继续

增长的时候,出现了Fortran、C、Pascal等高级语言,这些高级语言使得编写复杂的程

序变得容易,程序员们可以更好地对付日益增长的复杂性。但是,假如软件系统达成一定

规模,即使应用结构化程序设计方法,局势仍将变得不可控制。作为一种减少复杂性的工

具,面向对象语言产生了,面向对象程序设计也随之产生。

3.1.2什么是面向对象程序设计

一方面我们要了解的是,什么是面向对象的程序设计方法oOP(ObjectOriented

Programming),它与传统的结构化程序设计方法SP(StructureProgramming)

的重要区别是什么。

要弄清什么是OOP,则应了解和回顾一下传统的结构化程序设计方法及其设计思想、

程序结构与特点,以便比较对照两者之间的差异,由此领略OOP对SP的继承、丰富、发

展和突破。

1.结构化程序设计思想

结构化程序设计SP是60年代诞生的,以针对当时爆发的所谓“软件危机”的挑战,

而在70、80年代遍及全球成为所有软件开发设计领域、每个程序员都广为采用的传统的

结构化程序设计方法与技术的简称。它的产生和发展形成了现代软件工程学的基础。

SP的总的设计思绪是两点:一是自顶向下,层次化;二是逐步求精,精细化。

其程序结构是按功能划分基本模块为树型结构,使模块间的关系尽也许简朴、独立,并

从而单独验证模块的对的性。每一模块均由顺序、选择和循环三种基本结构组合而成。综

言之,此即所谓“模块化”。

因此,SP的程序的基本特点是:

a.按层次组织模块。(即战略上划分战役)

b.每一模块只有一个入口,一个出口。(每一战役尽也许简朴、明确)

c.代码和数据分离实现。(战术上程序=数据结构+算法)

SP实现上述战略战术的具体方法和技术是:使用局部变量和子程序。

SP的优点可以概括为:

a.子程序对程序其它部分没有或尽也许少的连带作用,从而为共享代码奠定基础。

b.由于SP方法的模块化分解与功能抽象,自顶向下,分而治之的手段及技术,从而能有效

地将一个复杂的、中大规模的程序系统的设计任务提成许多易于控制和解决、可独立编程

的子任务、子程序模块。

c.对于上述子程序或模块中的每一个都有一个清楚的抽象界面,它只说明:相应用程

序设计者来说只需了解它做什么(Whattodo),而不必说明:它如何去做(Howtodo)。

但从本质上说,由Pascal和C这样的程序设计推动的传统的SP仍是一种面向数

据和过程的设计方法,它把数据和过程分离为互相独立的实体,用数据代表问题空间中的

客体借以表达实际问题中的信息;程序代码则体现用于解决加工这些数据的算法。

于是,程序员在编程时必须时刻考虑要解决的数据结构和类型,对不同的数据格式(结

构和类型)即使要作同样的解决计算,或者对于相同的数据格式要作不同的解决都要编写不

同的程序,可见,使用传统SP方法设计出来的程序或系统,其可食用的成分很少。另一方

面,当把数据和代码作为不同的分离体时,总存在着用错误的数据调用对的的程序,或用对

的的数据调用错误的程序的危险。因而,使数据与程序始终保持相容一致,已成为程序员的

一个沉重的承担。这就是为什么在开发一个大型软件的过程中,假如用户在工程的中、后期

对数据格式或实现方案提出任何改变请求时,变化摧毁了前面工作的一切,前功尽弃。

2.面向对象程序设计思想

为了克服和解决当今许多大型软件项目和系统设计都接近或达成了SP方法所难以控

制解决和适应其变化的上述种种矛盾及问题而产生了OOP方法与技术。OOP吸取了SP

的一切优点和长处,同时又正视和顺应现实世界由物质和意识两部分组成的这一普遍原理,

将其映射到对象的解空间就是:具体事物一一对象,抽象概念一一类;而一个对象无非就是

这样一个实体,它具有一个名字标记,并带有自身的状态(属性)和自身的功能(行为或方

法)。世界上的所有事物(对象)就是如此奇妙地简朴。这正是面向对象方法和技术所追求的

目的:将世界上的问题尽也许简朴化。

事实上,用计算机求解的问题都是现实世界中的问题,它们无非都是由一些互相联系的,

并处在不断运动变化的事物(即对象)所组成。每个具体对象都可用两个特性来把握:描

述事物静态数据和可施于这些数据上的有限操作。也就是说,应当把数据结构和对数据的操

作放在地起,作为一个互相依存不可分离的整体(即对象类)来解决(封装、信息隐蔽、数

据抽象等)。并且要考虑不同事物即对象类之间的联系(消息、通讯、协议等)和事物即对象

类的重用性和变化性(继承、多态等),这才更符合客观世界的本来面目。

概言之,OOP与SP不同,它除了包纳了SP的一切优特点与机制外,同时又是一个引

入若干强有力的、更能反映事物本质的新概念、新机制,从而开创了一个程序新天地的强

人新方法。

OOP的基本原理是:用问题领域的模型来模拟现实世界,从而设计出尽也许直接、自然

地表达问题求解方法的软件,这样的软件系统由对象组成,而对象则是完整反映客观世界

事物具有不可分割的静态属性(数据结构)与动态行为(方法)的,并且它们是既有联系又有

变化发展的实体。

OOP方法所涉及的新概念,在理论上最具一般性的相应物及其描述如下等等:

a.对象一一对客观世界事物的表达或描述,世界上任何具体事物均可称为对象。

b.类一一是一组对象的抽象定义(抽象概念、抽象数据类型)

c.方法相应于对象的功能。

d.消息一一对象之间通讯的途径。

e.继承一一对象间具有相同性和差异变化的关系。

基于以上概念,面向对象就成了一个作为现实世界映射的封闭的缩微世界,一个对象具

有自身的属性(私有数据成员)和可认为自己或别人工作的功能(方法、操作或称成员函数),

它能通过发送消息与其它对象进行通讯,协同完毕任务。

OOP并不是用一两句话就可以精拟定义描述清楚的一套方法与技术,然而在这里,我

们还是非形式化地、笼统地说明和描述一下OOP的概念:OOP是SP、信息掩蔽、知识

表达、并行解决领域等概念的继承和发展。OOP的特点是把系统设计成将所需规定解的问

题分解成一些对象及对象间传递消息的符合客观世界规律的自然过程。OOP方法使程序

员摆脱具体的数据格式和过程的束缚,将精力集中到对要解决的对象的设计和研究上,从而

大大减少了软件开发的复杂度。OOP涉及了功能抽象的抽象数据、信息隐蔽即封装等机理,

使对象的内部实现与外界隔离,从而提供了更抱负的模块化机制,大大减少了程序间的互相

干扰和副作用。OOP的抽象数据类型一一对象类及继承,则为我们提供了抱负的高可重

用性的软件成份和机制。

此外,在人工智能领域中,若用OOP方法表达知识,则更接近于自然的客观世界的知

识表达和结识论,因而不仅能表达描述非常复杂的客观事物和知识,并且具有模块性强、

结构化限度高,便于分层实现,有助于实际开发和维护。因此OOP方法和技术的优特点将

非常适合知识解决、知识库、专家系统等人工智能领域和数据库、CAD、图形解决(多

媒体)、系统模拟与构造等大型复杂软件工程化开发。

3.1.3面向对象程序设计语言

一个语言要称为面向对象语言必须支持几个重要面向对象的概念。根据支持限度的不

同,通常所说的面向对象语言可以提成两类:基于对象的语言,面向对象的语言。

基于对象的语言仅支持类和对象,而面向对象的语言支持的概念涉及:类与对象、继

承、多态。举例来说,Add就是个典型的基于对象的语言,由丁它不支持继承、多态,此外

其他基于对象的语言尚有A1phard、CLU、Euclid>Modulao面向对象的语言中一部

分是新发明的语言,如Smalltalk、Java,这些语言自身往往吸取了其他语言的精华,

而又尽量剔除他们的局限性,因此面向对象的特性特别明显,充满了蓬勃的生机;此外一

些则是对现有的语言进行改造,增长面向对象的特性演化而来的。如由Pascal发展而来

的ObjectPasca1,由C发展而来的Objective-C,C++,由Ada发展而来的Ada9

5等,这些语言保存着对原有语言的兼容,并不是纯粹的面向对象语言,但由于其前身往往

是有一定影响的语言,因此这些语言仍然宝刀不老,在程序设计语言中占有十分重要的地

位。

C++语言及环境中的OOP有五个最基本概念,这五个基本概念术语是构成整个C++

面向对象方法与技术的柱石。它便是:

a.对象(Object)

b.消息(Message)

c.方法(Method)

d.类(C1ass)

e.继承(Inheritenee)

下面我们将分别介绍它们的含义、组成和性质。

1.对象

什么是对象?对象是私有数据及可以对这些数据施加的操作结合在一起所构成的独

立实体。即一个对象基本上涉及两个重要部分:记载对象内部状态的数据和表现对象活动

与功能的程序。

从传统的SP观点来看,数据和解决它们的代码是两个不同的独立实体,它们之间的

对的联系、选择和匹配需要应用系统的设计者时刻考虑。而OOP中,一个对象则是由私

有数据和其上的一组操作代码组成的一个统一体。请看如下示意图:

对象的动作取决于发送给该对象的消息表达式,消息告诉对象规定完毕的功能,并激活

该功能,这意味着对象具有自动“知道”如何完毕相应操作代码的“智能”选择机制,正是

这一机制把sP中应用系统程序员作出的选择操作数据与相应操作函数代码匹配的承担转

移给了系统设计者。

上述所谓的“智能化”的选择机制并不神秘,只但是是在对象的操作(过程或函数)

表中对消息操作名进行按名搜索而已,相应C++来说,执行一个消息表达式就是触发选择机

制在相应对象的调度表(内存控制块)中进行搜索,找到后便转向相应于该消息的成员函数

代码中运营。

事实上,除了上述选择匹配机制是新东西外,下面这些术语的含义和传统的SP相应名

词及说明是相以相应的:

对象——内存中的数据块

对象标记符一一指向上述数据的指针

向对象发送消息一一使成员函数作用于数据

在OOP中,数据和代码是紧密结合在一起的计算机中的一内存分块。但局部于该对

象中的数据只可以由对象中的代制块(成员函数)来访问,即数据是对象私有的(受保护的),

不能被其它对象影响和修改。简朴地说,一个对象从不关心其它对象的内部细节,而只关心

它自己,对象之间的通讯只是以“做什么”的消息发送为契机,并且认为接受消息的对象是

知道如何去做的。

对象可以非常简朴,也可以十分复杂。通常复杂对象由简朴的对象层层嵌套组合而成。

此后我们将从C++程序实例中看到,对象形式上只是系统程序员、应用程序员或用户定

义的“类”这种数据结构类型的变量,当我们定义一个对象,就已发明出了一种新的抽象

数据类型。对象可以说是构成和支撑整个OOP最重要的细胞与基石。除了上述基本组成、

关系和机理特性外,对象尚有以下性质和优点:

a.模块独立性。逻辑上看,一个对象是独立存在的模块,从外部看这模块,只需了解它

具有哪些功能,至于它如何实现这些功能和使用哪些局部数据来完毕它们的细节则“隐蔽”

在模块内部。这意味着模块内部状态不受外界干扰改变,也不会殃及到其它模块。即模块

间依赖性极小或几乎没有;各模块可独立为系统组合选用,也可被程序员重用,而不必紧张

波及或破坏别的模块。

b.动态连接性。客观世界中各式各样的对象,并不是孤立存在的,正是它们之间的互相

作用、联系和连接,才构成了世间各种不同的系统。同时,在OOP中,通过消息(传送)激

活机制,把对象之间动态联系连接起来,便称为对象的动态连接性,

c.易维护性。由于对象的功能被“隐蔽”和好象被一层封装壳保护在对象内部,故无

论是修改,还是功能完善以及实现的细节都被囿于该对象内部,不会播及到外部去,这增长

了对象和整个系统的易维护性。

2.消息

什么是消息?消息是规定某个对象执行其中某个功能操作的规格说明。

通俗地讲,OOP中的术语“消息”只但是是现实世界中的“请求”、“命令”等平常生

活用语的同义词。

通常,00P的一条消息由选择器(“消息操作”或“消息名”)及若干变元(0个或多

个实参)和接受消息的对象三个部分组成。

从程序设计语言的角度出发来说,除前面曾经提到过的消息具有提供(激活)对象的动

态选择机制外,消息表达式从形式上看类似于普通的函数调用。

如代码:

switch(message)

(

caseW_LBUTTONDOWN;//鼠标左按钮被按下

break;

caseWM_LBUTTONUP;//鼠标左按钮被放开

break;

)

一般,我们把发送消息的对象称为发送者,接受消息的对象称为接受者。对象间的联

系,只能通过传送消息即执行消息表达式来进行。对象也只有在收到消息时,才被激活,

补激活的对象的代码将知道如何去进行操作它的私有数据,以完毕所发送的消息规定的功

能。消息发送不考虑具体的接受者,对象可以响应,也可以不响应。

3.方法

什么是方法?OOP中的术语“方法”相应于对象的能力,即它是实现对象所具有的功

能操作的代码段,是响应消息的“方法”。

C++中,方法即是类中定义的成员函数,它只但是是该类对象所能执行的操作的算法

实现而己。

方法与消息是相应的,每当对象收到一个消息,它除了能用“智能化”的选择机制知

道和决定应当去做什么(Whattodo)外,还要知道和决定该如何做(Howtodo)0

而方法正是与对象相连决定怎么做的的操作执行代码。

4.类

什么是类?类是对一组对象的抽象归纳概括,确切地说,类是对一组具有相同数据成员

和相同操作成员的对象的定义和说明。而每个对象都是某个类的一个具体实例。

在OOP中,每个对象由一个类来定义或说明,类可以看作生产具有相同属性和行为方

式对象的模板。类或分类就是所谓“物以类聚,人以群分”的意思,“类”就是具有相似性

质的事物的同类特性的集中。简言之,按照对象的相似性,我们把对象提成一些类和子类,

故相似对象的集合即称为类。对C++程序员而言,类事实上是一种对象类型。它描述属于该

类型的具有相同结构和功能的对象的一般性质。而结构类型则是只有将相关信息(仅只是

不同类型的数据集)集中在一起的一个C的变量集。

从理论上讲,类(class)是一组同类对象的抽象,它将该种对象的共同特性,涉

及操作特性和存储特性都集中封装起来。由属于此类的对象共享。例如,讨论食肉动物时,

我们并不只是特指老虎利狮子,而只是抽象出它们的共同特性:比如喜欢吃肉。更具体地,

又如:若为屏幕上的点设计一个类如下:

classpoint

(

x,y;

new();

deletc();

move();

highlight();

}

则可以说明point类的对象如:

pointa(100,50),b(30,60);

piont中定义的数据是抽象数据类型,而对象a,b中的数据则是对象相应着内存中

的具体数据单元拷贝块,以其初始化时的具体值和存储于不同单元块而区分不同的对象。

5.继承

什么是继承?继承是对象类间的一种相关关系,并具有以下关键内容:

a.类间的共享的特性(涉及共享程序代码和数据)。

b.类间的细微区别或新增部分。

c.类间的层次结构。

继承机制既是一个对象类就获得另一个对象类特性的过程,也是一个以分层结构组织、

构造和重用类的工具。它是解决客观对象“相似又不同”的方法。

具体地讲,若类B继承了类A,则属于类B中的对象便具有类A的一切性质和功能。

我们称A为基类或父类,而称B为A的派生类或子类。因此,要描述或构造一个新类B,只

须去继承一个与之有共同特性的父类A,然后描述与父类不同的少量特性(即增长一些新的

数据成员或成员函数)即可。

继承机制有以下几个优特点:

a.能清楚体现相似类间的层次结构关系。

b.能减少代码和数据的反复冗余度,增强程序的重用性。

c.能通过增强一致性来减少模块间的接口和界面,增强程序的易维护性。

d.继承是能自动传播代码的有力工具。

e.继承还是在一些比较一般的类的基础上构造、建立和扩充新类的最有效的手段。

假如没有继承概念的支持,则OOP中所有的类就象一盘各自为政、彼此独立的散沙,

每次软件开发都要从“一无所有”开始。

C++中,除了一般OOP方法的树型层次结构的单一继承外,还支持多重继承。对单一

继承,每个子类只能有一个父类,而对多重继承,每个子类则可以有多个父类。

3.1.4面向对象程序设计的优点

面向对象出现以前"结构化程序设计是程序设计的主流,结构化程序设计又称为面向

过程的程序设计。在面向过程程序设计中,问题被看作一系列需要完毕的任务,函数(在

此泛指例程、函数、过程)用于完毕这些任务,解决问题的焦点臬中于函数V其中函数是

面向过程的,即它关注如何根据规定的条件完毕指定的任务。

在多函数程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数

访问。每个函数都可以具有它们自己的局部数据。下图显示了一个面向过程程序中函数和

数据的关系。

这种结构很容易导致全局数据在无意中被其他函数改动,因而程序的对的性不易保

证。面向对象程序设计的出发点之一就是填补面向过程程序设计口的一些缺陷:对象是程

序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地

改变。下图显示了一个面向对象程序中对象与函数和数据的关系。

比较面向对象程序设计和面向过程程序设计,还可以得到面向对象程序设计的其他优

占•

⑴数据抽象的概念可以在保持外部接口不变的情况下改变内部实现,从而减少甚至

避免对外界的干扰;

⑵通过继承大幅减少冗余的代码,并可以方便地扩展现有代码,提高编码效率,也减

低了犯错概率,减少软件维护的难度;

⑶结合面向对象分析、面向对象设计,允许将问题域中的对象直接映射到程序中,减

少软件开发过程中中间环节的转换过程;

(4)通过对对象的辨别、划分可以将软件系统分割为若干相对为独立的部分,在一定

限度上更便于控制软件复杂度;

⑸以对象为中心的设计可以帮助开发人员从静态(属性)和动态(方法)两个方面把握

问题,从而更好地实现系统;

(6)通过对象的聚合、联合可以在保证封装与抽象的原则下实现对象在内在结构以及

外在功能上的扩充,从而实现对象由低到高的升级。

3.1.5面向对象程序设计重要特性

面向对象程序设计重要特性有三个:继承性、封装性和多态性。

1.继承性

继承性在上面己作介绍,不再反复。

2.封装性

OOP中的封装性是一种信息掩蔽技术,它使系统设计员可以清楚地标明他们所提供的

服务界面,用户和应用程序员则只看得见对象提供的操作功能(即封装面上的信息),看

不到其中的数据或操作代码细节。从用户或应用程序员的角度讲:对象提供了一组服务,

而服务的具体实现(即对象的内部)却对用户屏蔽。如下图所示:

从用户和系统设计者看封装,用户只能看见对象提供的服务,而看不到服务的具体实

现,只有系统设计者才干看见封闭在对象中数据及其过程。

对象的这一封装机制的目的在于将对象的设计者和使用者分开,使用者不必知道对象

行为实现的细节,只须用设计者提供的消息命令对象去做就行了。

总之,我们可以这样来定义封装:

a.一个清楚的边界。对象的所有私有数据、内部程序即方法(成员函数)细节被囿定

在这个边界内。

b.一个接口。这个接口描述了对象之间的互相作用,请求和响应,它就是消息。

c.对象内部实现代码受到封装壳的保护,其它对象不能直接修改本对象拥有的(私有

的)数据和代码,只有通过本对象类的内部代码(成员函数)来修改。

这就是OOP中对象的封装性。

3.多态性

OOP支持多态性,它指的是相同的函数调用,被不同的对象接受时,可导致不同的行为。

运用多态性,我们可以把函数的不同的实现细节留给接受函数调用的对象,而程序中则用

同一函数名进行一般形式的调用。例如,消息函数调用Print()被发送到一图形对象时和

将其发送到一正文对象时的结果肯定不同样。

例如,有三个函数:

abs()返回整数绝对值

1abs()返回long的绝对值

fabs()返回double的绝对值

尽管这三个函数执行几乎相同的操作,在SP中,须用三个不同的函数说明及调来表达

和使用它们,即大同小异的函数也必须有不同的函数名。于是,虽然仅只是一个求绝对值

的运算行为,程序员必须至少记住三件事(三个函数)。

在C++中,则改变了这一传统做法,可以用一个名字来表达和访问这三个函数而不会

出现混乱。即如下:

#include<iostream.h>

intabs(inti){returni<0?-i:i;}

doubleabs(doubled){returnd<0?—d:d;}

longabs(long1){return1<0?-1:1;}

main()

cout<<abs(—10)<<“\n";

cout<<abs(-11.0)«“\n";

cout<<abs(-9L)<<"\n"

它们是不同的函数,却使用同一名字abs,当程序中调用abs时,OOP编译器会根据自

变量类型自动录找对的的那个函数进行运算,这就将要记住的三件事减少为只须记住一件

事了,从应用的角度来看,就好象一个函数支持三种不同的运算(功能)同样,这就是函数

的多态性。

除以上三大特性之外,面向对象程序设计还具有动态连接、消息驱动等特点。

所谓连接,是把例程地址送给例程调用者的过程。在运营前的编译时便以目的代码的

形式与系统完毕连接称为静态连接,而在运营时进行上述连接的则称为动态连接。

传统的C仅支持静态连接,而面向对象的程序设计语言在其实现中使用了动态编译技

术,因而允许以目的代码的形式在运营过程中才与系统进行连接。这使得系统的灵活性和

方便性大大增强。

OOP中的消息驱动机制不同于SP中的子程序调用,这是由于,子程序调用者与被调用

者有控制与被控制的关系,且凡调用必须有返回。而消息驱动则是当一个对象想激活另一

个对象的某个功能时,它把请求以消息的形式传送给接受者就算了事,至于接受对象如何

行动,如何解决该消息,完全是接受者自行决定的私事,其行为既不受发送者的控制,也不

一定有求必应和必有返回。

下图给出一个解决键盘输入的消息驱动过程:

Windows

注意,上述过程中,每个应用程序将有被创建的一个相应的应用程序队列。

3.1.7面向对象设计方法的特点和面临的问题

面向对象设计方法以对象为基础,运用特定的软件工具直接完毕从对象客体的描述到

软件结构之间的转换。这是面向对象设计方法最重要的特点和成就。面向对象设计方法的

应用解决了传统结构化开发方法中客观世界描述工具与软件结构的不一致性问题,缩短了

开发周期,解决了从分析和设计到软件模块结构之间多次转换映射的繁杂过程,是一种很

有发展前程的系统开发方法。

但是同原型方法同样,面向对象设计方法需要一定的软件基础支持才可以应用,此外

在大型的MIS开发中假如不经自顶向下的整体划分,而是一开始就自底向上的采用面向对

象设计方法开发系统,同样也会导致系统结构不合理、各部分关系失调等问题。所以面向

对象设计方法和结构化方法目前仍是两种在系统开发领域互相依存的、不可替代的方法。

3.2类和对象

3.2.1类和对象概述

1.类和对象的声明

在C程序设计中我们学习过结构体,它的掌握非常重要,重要在哪里呢?重要在结构体

和类有相同的特性,但又有很大的区别,类是构成面向对象编程的基础,但它是和结构体

有着极其密切的关系。

我们在C语言中创建一个结构体我们使用如下方法:

structtest

(

private:

intnumber;

public:

floatsocre;

};

类的创建方式和结构体几乎同样,看如下的代码:

classtest

(

private:

intnumber;

public:

floatsocre;

pub1ic:

intrp()

(

returnnumber;

)

voidsetnum(inta)

{

number=a;

)

);

但是大家注意到没有,标准C中是不允许在结构体中声明函数的,但C++中的类可以,

这一点就和C有了本质的区别,很好的体现了C++面向对象的特点!

过去的C语言是一种非面向去象的语言,它的特性是:程序二算法+数据结构。但C++

的特性是:对象=算法+数据结构;程序:对象+对象+对象+对象+........所以根据这一

特性,我们在定义一个自己定义的结构体变量的时候。这个变量就应当是叫做对象或者叫

实例。

例如:

testa;

那么a就是test结构的一个对象(实例),test结构体内的成员可以叫做是分量,例

如:

a.socre=10.1f;

那么number就是test结构的对象a的分量(或者叫数据成员,或者叫属性)sc。r

e;

在C语言中结构体中的各成员他们的默认存储控制是public而C++中类的默认存

储控制是private,所以在类中的成员假如需要外部掉用一定要加上关键字public声明

成公有类型,这一特性同样使用于类中的成员函数,函数的操作方式和普通函数差别并不

大。

例如上面的例子中的rp()成员函数,我们假如有如下定义:

testa;

调用rp()就应当写成:

a.rp();

成员函数的调用和普通成员变量的调用方式一致都采用.的操作符。

下面给出一个完整的例子:

#include<iostream.h>

c1asstest

(

private:〃私有成员类外不可以直接访问

§intnumber;

。publie:〃共有成员类外可以直接访问

floatsocre;

叩ublic:

intrp()

returnnumber;

avoidsetnum(inta)

(

3number=a;

g}

};

voidmain()

(

•testa;

®//a.number=10;//错误的、私有成员不能外部访问

a.socre=99.9f;

•cout<<a.socre«cndl;//公有成员可以外部访问

a.setnum(100);//通过公有成员函数setnum()对私有成员number进行

赋值操作

cout«a.rp();〃间接返回私有成员number的值

cin.get();

)

运营结果:

99.9

100

2,在类体外定义成员函数

(1)内联函数和外联函数。类的成员函数可以分为内联函数和外联函数。内联函数是

指那些定义在类体内的成员函数,即该函数的函数体放在类体内,而说明在类体内,定义

在类体外的成员函数叫外联函数。外联函数的函数体定义在类的实现部分。

内联函数在调用时不是象一般函数那样要转去执行被调用函数的函数体,执行完毕后

再转回调用函数中。而是在调用函数处用内联函数体的代码来替换,这样将会节省调用开

销,提高运营速度。

内联函数与前面讲过的带参数的宏定义比较,它们的代码效率是同样的,但是内联函

数要优于宏定义,由于内联函数遵循函数的类型和作用域规则,它与一般函数更相近,在

一些编译器中,一旦关上内联扩展,将与一般函数同样进行调用,调试比较方便。

在类体外定义的外联函数可以用一种形式转变成内联函数,即在函数头加上关键字

iniine就可以了。

⑵重载性。两个以上的函数,取同一个名字,只要使用不同类型的参数或参数个数

不同,编译器便知道在什么情况下,调用哪个函数。这就叫做函数重载。

成员函数可以进行重载。构造函数可以重载,而析构函数不能重载。一般的成员函数

都可以重载。

上面介绍了在类内部定义成员函数(方法)的方法,下面我们要介绍一下域区分符(::)

的作用。即在类外部定义成员函数。

下面我们来看一个例子,运用这个例子中我们要说明几个重要问题:

nclude<iostream.h>

intpp=0;

classtest

private:

intnumber;

publie:

floatsocre;

intpp;

pub1ic:

voidrp();

);

VOidtest::rp()//在外部运用域区分符定义test类的成员函数

(

::PP=11;〃变量名前加域区分符给全局变量pp赋值

PP=100;〃设立结构体变量

}

voidmain()

(

testa;

testb;

a.rp();

cout<<pp<<endl;

cout<<a.pp«endl;

cin.get();

)

运营结果:

11

100

问题一:

运用域区分符我们可以在类定义的外部设立成员函数,但要注意的是,在类的内部必

须预先声明:

voidtest::rp()

在函数类型的后面加上类的名称再加上域区分符(::)再加函数名称,运用这样的方

法我们就在类的外部建立了一个名为rp的test类大成员函数(方法),也许很多人要问,

这么做故意义吗?在类的内部写函数代码不是更好?

由于,在类的定义中,一般成员函数的规模一般都比较小,并且一些特殊的语句是不

可以使用的,并且一般会被自动的设立成为inline(内联)函数,即使你没有明确的声明

为:nline,那么为什么有会被自动设立成为inline呢?由于大多数情况下,类的定义一般

是放在头文献中的,在编译的时候这些函数的定义也随之进入头文献,这样就会导致被多

次编译,假如是inline的情况,函数定义在调用处扩展,就避免了反复编译的问题,并且把

大量的成员函数都放在类中使用起来也十分不方便,为了避免这种情况的发生,所以C++

是允许在外部定义类的成员函数(方法)的,将类定义和其它成员函数定义分开,是面向

对象编程的通常做法,我们把类的定义在这里也就是头文献了看作是类的外部接口,类的

成员函数的定义当作是类的内部实现。写程序的时候只需要外部接口也就是头文献即可,

这一特点和我们使用标准库函数的道理是一致的,由于在类的定义中,已经包含了成员函

数(方法)的声明。

问题二:

域区分符和外部全局变量和类成员变量之间的关系。在上面的代码中我们看到了,外

部全局和类内部都有一个叫做PP的整形变量,那么我们要区分操作他们用什么方法呢?

使用域区分符就可以做到这一点,在上面的代码中::PP-11;操作的就是外部的同名

称全局变量,PP=100;操作的就是类内部的成员变量,这一点十分重要!

问题三:

一个类的所有对象调用的都是同一段代码,那么操作成员变量的时候计算机有是如何

知道哪个成员是属于哪个对象的呢?

这里牵扯到一个隐藏的this指针的问题,上面的代码在调用a.rp()的的时候,系

统自动传递一了个a对象的指针给函数,在内部的时候pp=100;的时候其实就是this

->PP=100;

所以你把上面的成员函数写成如下形势也是对的的:

voidtest::rp()

::pp=ll;

this->pp=100;//this指针就是指向a对象的指针

)

类的成员函数和普通函数同样是可以进行重载操作的,关于重载函数,我们将在后面

讨论,这里只给出一个例子看看:

#inc1ude<iostream.h>

c1asstest

(

private:

intnumber;

public:

f1oatsocre;

intpp;

pub1ic:

voidrp(int);

voidrp(float);

);

voidtest::rp(inta)//'在外部运用域区分符定义test类的成员函数

(

cout<<”调用成员函数!a:^<<a<<end1;

}

voidtest::rp(floata)〃在外部运用域区分符定义test类的成员函数

(

coutV〈”调用成员函数!a:"<<a<<endl;

)

voidmain()

testa;

a.rp(100);

a.rp(3.14f);

cin.get();

)

运营结果:

调用成员函数!a:100

调用成员函数!a:3.14

3.运用指针和引用调用类的成员函数

下面我们来看一下运用指针和运用引用间接调用类的成员函数,对于指针和引用调用

成员函数和调用普通函数差别不大,在这里也就不再反复说明了,注意看代码,多试多练习

即可。

代码如下:

#include<iostream.h>

classtest

(

private:

intnumber;

public:

floatsocre;

intpp;

public:

intrp(int);

);

inttest::rp(inta)//在外部运用域区分符定义test类的成员函数

number=100;

returna+number;

}

voidrun(test*p)//运用指针调用

(

cout«p->rp(100)<<endl;

)

voidrun(test&p)〃运用引用

(

cout<<p.rp(200)«endl;

}

voidmain()

(

testa;

run(&a);

run(a);

cin.get();

)

运营结果:

200

300

4.类成员的保护性

前面我们说过,类的成员假如不显式的生命为public那么它默认的就是private

就是私有的,私有声明可以保护成员不可以被外部访问,但在C++尚有一个修饰符,它具有

和private相似的性能,它就是protected修饰符。

在这里我们简朴说明一下,他们三着之间的差别:

在类的private:节中声明的成员(无论数据成员或是成员函数)仅仅能被类的成员函

数和友元访问。

在类的protected:节中声明的成员(无论数据成员或是成员函数)仅仅能被类的

成员函数,友元以及子类的成员函数和友元访问。

在类的public:节中声明的成员(无论数据成员或是成员函数)能被任何人访问。

由于private和protected的差别重要是体现在类的继承中,现在的教程还没有设

计到友元和子类所以这里不做进一步讨论,但上面的三点务必记得,在以后我们会回过头

来说明的。

总的来说,类成员的保护无非是为了以下四点:

⑴相对与普通函数和其它类的成员函数来说,保护类的数据不可以被肆意的篡改侵

犯!

(2)使类对它自身的内部数据维护负责,只有类自己才可以访问自己的保护数据!

⑶限制类的外部接口,把一个类提成公有的和受保护的两部分,对于使用者来说它

只要会用就可以,无须了解内部完整结构,起到黑盒的效果。

(4)减少类与其它代码的关联程,类的功能是独立的,不需要依靠应用程序的运营环

境,这个程序可以用它,此外一个也可以用它,使得你可以容易的用一个类替换另一个类。

下面为了演示类成员的保护特性,我们来做一个球类游戏!

我们设计一个类,来计算球员的平均成绩,规定在外部不可以随意篡改球员的平均成

绩。

我们把该类命名为ballscore并且把它放到bal1score.h的有文献中!

classballscore

叩rotected:

«>intgbs;〃好球单位得分

intbbs;//坏球单位扣分

«floatgradescore;//平均成绩

®public:

voidinit()

gbs=5;

abbs=-3;

4}

。floatGetGS(f1oatgoodball,floatbadball)//goodba11好球数,

badba11坏球数

(

。gradescore=(goodbal1*gbs+badba11*bbs)/(goodball+badb

all);

areturngradescore"/返回平均成绩

)

);

主函数调用:

#include<iostream.h>

#include"ballscore.h〃

voidmain()

(

®ballscorejeff;

^cout«jeff.GetGS(10,3);

//jeff.gradescore=5.5;//想篡改jeff的平均成绩是错误的!

jeff.init();

cin.get();

}

在上面的代码中头文献和类的使用很好了体现了类的黑盒特性,谁也不可以在外部修

改球员的平均成绩。

5.类的作用或

下面我们说一下关于类的作用域。在说内容之前我们先给出这部分内容的一个完整代

码,看讲解的是参照此一下代码。

#include<iostream,h>

c1assba11score

(

oprotected:

ointgbs;〃好球单位得分

ointbbs;//坏球单位扣分

。floatgscore;〃平均成绩

pub1ic:

svoidinit()

。(

•»gbs-5;

obbs=-3;

a}

of1oatGetGS(floatgb,floatbb)

8intgscore=0;//'定义一个同名的类成员函数局部变量

®ballscore::gscore=(gb*gbs+bb*bbs)/(gb+bb);//由于

同名须加类名和域区分符

weturnballscore::gscore;//返回平均成绩

a}

};

intballscorc=0;〃定义一个与类名称相同的普通全局变量

inttest;

voidmain()

c1asstest〃局部类的创建

。{

gfloata;

8floatb;

);°

®testtest;

::test=l;〃由于局部类名隐藏了外部变量,使用时需加域区分符

oclassba11scorejeff;〃由于全局ballsocre和类名称相同,需力口c1as

s前缀

jeff.init();

cout«jeff.GetGS(10,3);

)

类的作用域是只指定义和相应的成员函数定义的范围,在该范围内,一个类的成员函

数对同一类的数据成员具有无限制的访问权。

3.2.1构造函数与析构函数

构造函数和析构函数是类的两个特殊的成员函数

1.构造函数

构造函数(constructor)是类的一个特殊的成员函数,它与类名同名”当定义该类的

对象时,构造函数将被系统自动调用用以实现对该对象的初始化。

构造函数不能有返回值,因而不能指定涉及void在内的任何返回值类型。

构造函数的定义与其他成员函数的定义同样可以放在类内或类外。

构造函数的定义格式为:

类名(形参说明)

函数体

}

构造函数既可以定义成有参函数,也可以定义成无参函数,要根据问题的需要来定。

注意:程序中不能直接调用构造函数,构造函数是在创建对象时由系统直接调用的,

因比,在构造函数中一般完毕初始化类成员变量的操作。

2.构造函数的重载

一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。

在类的成员函数的重载中,比较常见形式是构造函数的重载,当类中出现了重载构造

函数时,C++语言将根据构造函数中的参数个数和类型选择合适的构造函数来完毕对象

的构造。

3.默认构造函数与缺省参数的构造函数

假如在类中没有显示定义构造函数,则编译系统会为该类提供一个默认的构造函数,

该默认构造函数是一个无参函数,函数体为空,它仅仅负责创建对象,而不做任何初始化

工作(即不给相应的数据成员赋初值),所以在该类的对象创建时不能保证有一个拟定的

初始状态。

良好的编程习惯应当是给类提供合适的完毕初始化工作的构造函数。但是,只要一个

类定义了一个构造函数(不一定是无参构造函数),编译系统就不再提供默认的构造函数。

当构造函数具有缺省参数时,称为具有缺省参数的构造函数,在使用品有缺省参数的构造

函数时,要防止二义性。

4.拷贝构造函数

拷贝构造函数是一种特殊的构造函数。定义拷贝构造函数的一般格式为:

类名::类名(const类名&形式参数)

函数体

拷贝构造函数的函数名与类名同名。该函数也没有返回值。拷贝构造函数的功能是通

过将一个同类对象的值拷贝给一个新对象,来完毕对新对象的初始化,即用一个对象去构

造比外一个对象。假如在类的定义中没有定义拷贝构造函数,则编译系统将自动生成一个

具有上述形式的默认的拷贝构造函数,作为该类的公有成员。

5,析构函数

与构造函数相应的是析构函数。当一个对象被定义时,系统会自动调用构造函数为该

对象分派相应的资源,当对象使用完毕后且在对象消失前,系统会自动调用类的析构函数

来释放这些系统资源。

析构函数也是类的一个特殊的成员函数,其函数名称是在类名的前面加上“〜”;它

没有返回值,也没有参数。一个类中只能拥有一个析构函数,所以析构函数不能重载。

析构函数的定义方式为:

、类名()

函数体

假如程序员在定义类时没有为类提供析构函数,则系统会自动创建一个默认的析构函

数,其形式为:

~类名O{}

对象被析构的顺序与其创建时的顺序正好相反,即最后构造的对象最先被析构。

假如一个对象是被new运算符动态创建的,当使用de1ete运算符释放它时,delet

e将会自动调用析构函数。

示例:

//tdate1.h

c1assTDate1

private:

intyear,month,day;

叩ub1ic:

TDate1(inty,intm,intd);

o~TDate1();

voidPrint();

};

TDatel::TDate1(inty,intm,intd)

(

yearly;

omonth=m;

day=d;

ocout<<z,Constructorcalled.\n";

)

TDate1::Datc1()

(

cout«z,Destructorcalied.\n〃;

}

voidTDatel::Print()

(

cout«year<<z/.H<<month«?z.*<<day«endl;

}

//tdatel.cpp

#include<iostream.h>

#inc1ude〃tdate1.h〃

intmain(void)

classTDateltoday(2023,3,9);

classTDateltomorrow(2023,3,10);

cout<<zztodayis〃;

today.Print();

。coutV<〃tomorrowis

tomorrow.Print();

<>return0;

)

运营结果:

Constructorcalled.

Constructorcalled.

todayis2023.3.9

tomorrowis2023.3.10

Dcstructorcalled.

Destructorcalled.

6.常对象与常对象成员

⑴常对象。常对象是指对象常量,其定义格式为:

const类名对象名;

从格式中可以看出,常对象的定义与一般对象的定义相比,在类名前必须加const关

键字。常对象具有以下特点:常对象在定义时必须进行初始化,并且在程序中不能再对其

进行更新;通过常对象只能调用类中的常成员函数,而不能调用类中的其他成员函数。

⑵常对象成员。常对象成员分为常成员函数和常数据成员。

一是常成员函数。在类中,使用关键字const说明的成员函数成为常成员函数,常

成员函数的说明格式为:

类型函数名(形参表)const;

类中的常成员函数与普通成员函数相比,具有以下特点:

常成员函数为类的只读函数,这种成员函数可以读取数据成员的值,但不可以更新数

据成员的值,它也不能调用该类中没有const修饰的其他成员函数。

常成员函数定义中的const关键字是函数类型的一部分,因此在其实现部分中也要

带上const关键字。

常成员函数定义中的const关键字可以参与区分重载函数。

例如:

^include<iostream.h>

classTest_const

(

®private:

。intm;

pub1ic:

West_const(intargl)//构造函数

。{

。m=arg1;

4

avoidsetv

温馨提示

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

评论

0/150

提交评论