面向对象程序设计第12章.docx
- 文档编号:11275012
- 上传时间:2023-02-26
- 格式:DOCX
- 页数:142
- 大小:105.58KB
面向对象程序设计第12章.docx
《面向对象程序设计第12章.docx》由会员分享,可在线阅读,更多相关《面向对象程序设计第12章.docx(142页珍藏版)》请在冰豆网上搜索。
面向对象程序设计第12章
第1章概述
面向对象程序设计,是一种以面向对象的思想和方法、以一种编程语言进行程序设计的过程。
在该过程中包含两个方面的含义:
其一,什么是面向对象的思想和方法,有什么优势,有什么作用;其二,在程序设计语言中是如何体现面向对象的思想和方法的,在使用过程中应该具备哪些基础,又该注意哪些问题。
本章首先从面向对象的基本概念出发,介绍面向对象的思想与机制、面向对象程序设计的优势、目前常用的程序设计语言和工具等内容。
由于在本书中需要用到大量的实例,其中会涉及到具体语言中的某些特性,所以在本章最后一部分给出相关的语言基础。
1.1面向对象思想与机制
面向对象(Object-oriented,简称OO)是一种思维方法,其核心思想即为归纳和演绎的方法。
实际上,人们认识世界和发展世界的过程就是面向对象的。
本节首先对人们认识世界和发展世界的过程进行分析,进而引出面向对象的基本概念和相关机制。
1.1.1人类认识世界和发展世界的过程
当一个新生儿从呱呱坠地后,便开始学习。
学习是过程是从认识一个又一个具体的“实物”开始的,这些实物包括动物(小猫小狗小鱼小鸟乃至小虫子)、植物(花草)和人(爸爸妈妈叔叔阿姨爷爷奶奶)。
先从这些单一实物中得到其个体特征,并逐步对这些个体特征进行综合得到共同特征。
从上幼儿园到小学、中学、大学,不断地上课,不断地接触学科和老师,在一门课中通过一个又一个的“例子”和“练习”得到提升。
这些过程都是归纳的过程,是一种从有形到无形的过程,也是一种从实例到知识的过程。
其总体表现形式为:
个体——个性——共性。
参加工作之后,需要将自己所学到的知识服务于社会,为社会发展作出应有的贡献。
在这个阶段,人们都需要将个体能力充分地发挥出来。
而发挥个人能力的方式有很多种,但归纳起来无外乎两种方法:
其一,通过知识融合——包括自身知识的融合以及借鉴前人的经验;其二,在自身已有知识或者他人已有经验的基础上发挥“想象”,添加一些“前所未有”的“创造”。
这两种方法正好是归纳过程的逆过程,也就是演绎的过程,是一种从无形知识到有形产品的过程。
其具体表现形式为:
共性——个性——个体。
在面向对象思想中,归纳被称为“抽象”,演绎被称为“派生”。
抽象和派生,构成面向对象的“继承”机制。
1.1.2面向对象的基本概念
1.1.2.1对象
从1.1.1我们得知,归纳的过程需要以大量的“实例”为基础。
这些实例称为“对象”。
对象在现实世界中也称为“实体”,在知识层面也称为“实例”。
为了更确切地理解对象的含义,可将其分为两大类:
物理对象(物理实体):
在现实生活中看得见摸得着具有一定实际尺寸的实际存在的“物体”。
比如:
张三(人),太阳,这只花猫,那棵白菜等。
逻辑对象(逻辑实体):
这类对象也是真实存在的,但不一定看得见摸得着。
比如课堂上的“例子”,法官断案时的“证据”,讲道理时所需要摆的“事实”等。
1.1.2.2类
世间万物,形形色色。
人们要认识世界,不可能需要认识世界上的每一个“物”,只需要认识有代表性的少数就够了。
通过举一反三的思想,便可以知道相似的“物”是什么。
物,是实际存在的。
而知道“物”是什么,则是知识,构成“类”。
因此,“类”是相似物体的高度抽象,或者说,类是相似对象的特征抽象。
在现实生活中,几乎所有的名词都是类,如:
桌子、星球、人、动物。
人们知道电脑,但未必一定要看得见一台电脑。
那么,在知识层面上,一个类是个什么样子呢?
简单来说,一个类都包含有若干“成员”,这些成员代表了同类对象的“特征”:
人,有姓名、年龄、身高、体重,也有思想,有动作,遇到突发事件时有回应。
树,有高度、宽度、命名,适度浇水可以生长,扒掉树皮就会死亡。
电脑,有品牌、显示器、主板、CPU,加电就会运行,点击就有反应。
从这几个例子可以看出,每个类的特征数量虽然有多有少,但都可以分为静态部分和动态部分。
前者称为物理特征,或属性,或数据,后者称为行为特征,或动作,或方法。
归结起来,一个类的由数据成员和方法成员构成的。
1.1.2.3类与对象
一个对象是个体存在,一个类是相同对象的共性。
一个类只具有特征,但每一个特征都没有具体值。
一个对象不仅具有特征,而且每一个特征都可以赋予具体值。
一个简单名词构成类。
而在该名字前加上“这个”、“那个”等限定词后则构成对象。
人们认识世界的过程,是从对象到类的过程。
人们发展世界的过程,是从类到对象的过程。
1.1.2.4类的组织
类是群体对象的抽象,不同类的对象将抽象出不同的类。
现实生活中的类很多,类和类之间的有机组织构成认识世界的基础。
类的组织结构有两种:
泛化特化结构:
类和类之间存在特征包含关系。
整体部分结构:
一个类的成员是另一个类的对象。
1.1.2.5消息传递
现实世界由对象构成。
由于存在的对象太多,所以要进行归类。
类只是一种抽象,表征了一种结构,是虚的,是一种意识形态,是一种认识同类对象的方法。
而世界是实际存在的。
要使世界运转,生活持续,必须构建出各种各样实实在在的对象,并且使用这些对象。
对象和对象之间是有关系的,彼此在“通信”和“交互”。
这种交互过程就称为对象之间的消息传递。
对象之间的消息传递是依靠“消息请求”、“消息处理”和“消息应答”过程来完成的。
其中的“消息请求”和“消息应答”存在与两个对象之间,“消息处理”则是对象内部的动作或活动。
消息传递机制可以有效地屏蔽消息处理的具体过程和内部细节,只对外呈现出相应的“接口”。
正如,人对外提供的接口包括“口”、“耳”、“眼”等,但“消息处理”的过程——大脑活动的细节和思维方法则是对外隐藏的。
1.1.3面向对象机制
面向对象机制包括封装、继承和多态。
1.1.3.1封装
面向对象的封装机制是其中最简单的一种机制,包含两个层面的含义:
一个对象,既有静止特征的成分,又有活动特征的成分。
将静态成分抽象为数据,活动成分抽象为方法,一个对象就是由其数据和方法进行“封装”后构成的“单元”。
而一个类又是对象特征的抽象,所以类的构成和对象的构成是一致的。
——这就是封装机制的第一层含义。
同时,作为一个对象而言,其特征部分有对外公开的,也有私密的(对外不可见)。
这种能够实现特征信息隐藏的能力构成面向对象封装机制的第二层含义。
在本书第2章将给出与封装机制相关的详细内容。
1.1.3.2继承
继承是面向对象三大机制中功能最为强大,使用也最为复杂多变的机制。
简单来说,所谓继承,是指用现有的类生成新类的机制。
现有的类可以是一个,也可以是多个。
生成的方式可以是派生,也可以是组合。
与继承相关的概念包括:
公有继承、私有继承和受保护继承;
父类、直接父类、间接父类、子类;
基类、派生类;
部分类、整体类;
单继承、多继承、虚继承。
在本书错误!
未找到引用源。
中给出与继承机制相关的详细内容。
1.1.3.3多态
作为面向对象的第三个机制,多态同样占据着非常重要的地位。
从现实生活来讲,多态可以通过以下两个层面进行理解:
一,相同的消息发送给不同的对象,产生不同的结果。
比如,在课堂上老师要求每个学生独立画一幅画。
相同的消息是“请画一幅画”,不同的对象是每个学生,结果肯定是多种多样的。
二,相同的消息发送给不同的对象,尽管产生的结果相同,但每个对象的内部实现原理不同。
比如,同样的刹车动作(踩刹车),无论是小汽车还是大货车都可以达到减速甚至停车的目的,但小汽车的刹车原理是“油刹”,而大货车则是“气刹”。
对于面向对象程序设计中的多态应用,不同的语言采用的方法不同。
在本书错误!
未找到引用源。
将给出C++语言中实现多态的方法和相关问题。
1.2面向对象设计与结构化设计
1.2.1基本编程方法比较
结构化程序设计的主体元素包括模块、函数和过程。
在C语言中,一个程序是有一个主函数和若干个非主函数所构成。
每一个函数都包括了函数头和函数体,函数体中又可以定义局部变量和代码片段。
而当人们采用结构化思想编写程序时,对于需要在多个地方出现的相同或者相似代码,就可以进行抽象以构成函数,以参数的形式代替有区别的地方,从而实现模块化结构。
比如,有下面的需求:
张三的语文书的第38页的第1个字符是什么?
李四的数学书的第50页的第8个字符是什么?
王五的外语书的第23页的第20个字符是什么?
经过分析后发现,这些需求的大体描述都是一样的,区别的地方有4个,分别是谁的书,什么书,第多少页,第多少个字符。
经过抽象,可以构造一个函数如【例1-1】所示。
【例1-1】结构化抽象的例子
chargetx(char*who,char*book,intpage,intpos)
{
//函数具体操作
}
voidmain()
{
cout< cout< cout< } 这种抽象是直接的,也是掌握了结构化程序设计方法之后最容易想到的方法。 但这种方法最大的问题在于后期的更新和维护。 一旦需求发生变化,或者环境发生了变化,getx函数就需要进行较大规模的调整,甚至是完全废弃。 比如: 不同人的同一名称的书是否是一样的。 同一种书的相同页面是否是一样的。 而如果每一个人每一本书上都不相同,那么采用这种函数抽象的意义就不复存在。 而面向对象程序设计方法主张以对象为单位。 需求中的人(包括张三、李四和王五)都是对象,不同的书(数学、语文、外语)也都是对象,每个对象彼此独立存在,互不干扰,从而构成下面的例子: 【例1-2】面向对象抽象的例子 voidmain() { cout<<张三.语文.C[38][1]; cout<<李四.数学.C[50][8]; cout<<王五.外语.C[23][20]; } 显然,【例1-2】还需要给出类和对象的定义,此处只是一个不完整的例子,只是说明面向对象编程的一个基本思想和方法。 在该例子中,张三的语文和李四的语文完全可以不同,彼此独立,能够很好地实现个体维护而不影响其他。 1.2.2面向对象方法的优势 简单来说,面向对象程序设计方法的优势在于能够很好地适应需求的变化,便于软件的升级。 可以考虑这样一个问题: 如果现在有一个正在运行的软件需要升级以满足用户的新的需求,应该如何做。 结构化方法的解决思路是: 打开源代码(如果有的话),分析源代码。 将不需要的需求删除,增加或修改源代码以满足新的需求。 而如果没有源代码,那么升级就变得不可能。 面向对象方法的解决思路是: 分析原始文档,对需要变化的类实施派生以产生新类,原有的类不变。 而在新类中,对于原来不能满足新需求的成员进行重写,最后适当修改主函数。 因此,面向对象程序设计的主要思想是以类为主导的,除了极其简单的主函数之外,其余部分都是类。 当需要对程序进行升级时,原有的类都不改变(有时因为类的实现被隐藏而根本无法修改),只需要对有些类进行派生以产生新类(可增加、不可修改也不可删除)。 总体来说,面向对象方法的优势体现在以下几个方面: 以类为单位组织代码,类间关系采用消息传递(函数调用)。 只要类间通信协议不变,类内部的任何修改都不会影响到其他,从而实现代码修改的“封闭性”。 当类的内部实现被隐藏之后,功能升级可以通过类的派生机制实现,同样可以满足需求变化。 而实际上,目前大量的商用“类库”都是以这种方式提供给用户使用的。 作为商用产品,大量成熟的基础“类库”的出现,为人们编写程序提供了极大的方便,诸如通信、多媒体、图形交互界面、数据库访问等。 1.2.3面向对象程序设计的一般过程 面向对象的思想是以类为主导的。 面向对象程序设计的过程也不例外。 一般来说,要构建一个面向对象程序,其过程如下: 一,构造类。 构造类的过程可以通过系统分析,对需求进行充分理解和关键词提取之后得到。 二,构建类和类之间的关系。 包括泛化特化关系和整体部分关系。 主要采用面向对象的继承机制(包括抽象、派生、组合等)。 三,创建应用类对象。 类是不能直接使用的。 要使用类中成员(数据成员和方法成员),必须创建对象。 应用类对象是独立存在的,类似于结构化程序设计中的主函数。 四,使用对象。 使用对象就是通过使用对象的成员来实现,通常是调用应用类对象中的一个主方法来实现。 可以看出,一个真正的面向对象程序是由若干个类所构成的。 C++中的主函数不能封装成类,必须是外部函数,但某些编程语言(如C#和java等)是将主运行函数都封装成类之后运行的。 但无论如何,主函数应该力求精炼和简短,如下例所示。 【例1-1】一个C++面向对象主函数 voidmain(){ CMyAppmyapp;//创建一个应用类对象 myapp.run();//调用应用类对象的run方法 } 1.3常用面向对象程序设计语言和工具 作为最经典的面向对象程序设计语言,C++是在C语言的基础上经过功能扩展而构成的。 C++完整地包含了C,所以C++也被称之为混合式的面向对象语言。 除了C++语言之外,基于.NET环境的C#,java,PowerScript等,都是使用较为广泛的面向对象程序设计语言。 就像C++需要VC环境一样,任何一种程序设计语言都离不开工具和环境。 语言本身只是一种规范,而要真正达到编写和运行,目前都是采用一种称为IDE(集成开发环境)的工具。 通常,不同的IDE适用于不同的开发语言。 常用的IDE和开发语言的对应关系如表1-1所示。 表1-1常用面向对象程序设计语言和工具 IDE 开发语言 VC++ C++ Delphi ObjectPASCAL C++Builder C++ VB 扩展BASIC .NET C#、J#等 Powerbuilder PowerScript Eclipse、Jbuilder等 Java 鉴于C语言的普及性,以及C++作为面向对象程序设计语言的经典性,本书采用C++作为编程语言。 在本书下面的介绍和例子中,如无特别说明,均以C++作为原型。 同时,考虑到例子的简洁性,所有需要用到的头文件均未列出。 如果需要以此为例进行实验,请各自增加必要的头文件。 1.4C++基础 1.4.1控制台IO流 由于在本书后续的例子中都需要用到输入输出流尤其是输出流,所以在本节开始首先作出相应的介绍。 输入输出流包括文件输入输出和控制台输入输出。 更详细的介绍参见错误! 未找到引用源。 。 控制台输入输出是最基本的练习方式,通常作为控制台程序中的人机交互的主要成分。 回顾C语言中的输入输出,有多种方式,包括getc、getchar、scanf、putc、putchar、printf等,尤其以scanf和printf用得最多。 但作为这两个函数调用时的关键部分,格式定义应该与变量类型一致,否则会产生错误的结果。 为了进一步简化输入输出函数的调用,C++中使用了IO流,其基本形式如下: 输入流: cin>>x>>y>>z; 输出流: cout< 其中,cin是将键盘输入内容依次分配给后续的变量,cout是将后续变量的值按照标准格式以此从控制台(显示器)上输出。 而在输入的过程中,还是应注意输入的值与变量之间的对应关系,包括数字部分和非数字部分、整数部分和小数部分等,不同数值之间也需要用空格分隔。 对于输出流cout,虽然从表面上看其输出顺序是从左到右,但处理顺序则是从右到左的。 【例1-1】输出流cout处理顺序和输出顺序 charf(){cout<<"A";return'B';} charg(){cout<<"C";return'D';} voidmain(){cout<<"E"< 处理顺序分析: 先执行函数g的调用,输出结果C,返回字符D(但没有输出),再执行函数f的调用,输出结果A,返回字符D(依然不输出),最后处理输出部分E(由于是常量,所以没有处理过程)。 在处理过程中就已经产生了CA输出。 所有的处理过程都结束之后,才得出了主函数cout的各个部分参数,分别为E、B和D,以此输出,形成EBD。 合并两部分输出,最后结果为CAEBD。 1.4.2引用 引用,是C++中的一种新特征,在提高代码的运行效率方面起着至关重要的作用。 1.4.2.1引用变量的定义 简单地说,C++中的引用就是别名机制。 而所谓别名,也就是在程序代码中多个名字表示着同样的意义,始终具有完全相同的取值。 从编译的角度来看,由于在代码中每个对象(变量)都对应着存储空间,所以,引用类型表征着不同名字的对象对应着相同的存储空间。 在C++中,引用的简单定义如【例1-5】所示: 【例1-1】引用变量的定义 intj; int&k=j; 对象j被定义为一个整型,系统将为其分配一个整型大小的存储空间。 而紧接着又定义了一个对象k。 但此时由于对象k被定义为一个引用(注意引用的定义与C++中指针的定义形式非常接近),而且是已知对象j的一个引用(即别名),因此,在整个程序代码中,对象k和对象j始终表示相同的存储空间,j的改变同时影响到k,反之亦然。 如【例1-6】: 【例1-2】使用引用变量 intj=0; int&k=j; j=10; cout< endl为回车换行。 k++; cout< endl为回车换行。 引用类型常常被用来作为函数的参数,可以有效提高函数调用的运行效率,减少参数传递时由实在参数创建形式参数的过程。 同时,引用类型还可以作为函数返回值类型。 下面就这两个方面给出相应的说明。 1.4.2.2引用类型作为函数参数 在函数调用时,通常需要用实在参数替代形式参数。 这种替代的过程实际上是用实在参数动态产生一个临时的形式参数空间的过程。 实在参数空间和形式参数空间彼此互为独立,但形式参数空间的值需要来自实在参数的值。 这无形中就产生了从一个空间到另一个空间的拷贝过程。 如【例1-7】所示。 【例1-1】函数调用时的参数传递 intf(inta,intb){returna+b;} voidmain() { intx=10,y=20,z; z=f(x,y); } 当程序运行到对函数f的调用语句时,其具体的执行流程是: 首先用实在参数y临时创建一个形式参数b空间并将b空间的值置为y的值,再用实在参数x临时创建一个形式参数a空间并将a空间的值置为x的值,其处理顺序是从右到左; 执行函数f的函数体,计算形式参数a和b的值之和; 为了返回结果,函数f再临时创建一个返回值空间,其值用a+b的值代替; 函数体执行完成,准备返回之前,释放形式参数a和b空间,释放顺序是先a后b,与创建的顺序相反; 函数调用完成,回到主函数,将函数返回值赋值给变量z,然后释放函数f的返回值空间。 上述调用函数的执行过程中临时创建了三个空间,其中有两个形式参数空间和一个返回值空间。 单就形式参数空间而言,不仅要创建,而且要赋值,赋值的过程就是把一个空间的值拷贝到另一个空间。 本例中的空间都是以整型为例,数值拷贝也只有4个字节。 但如果形式参数的类型不是整型而是一个结构,且该结构体的大小有成千上万个字节,数值拷贝的过程所占用的时间资源就不能不考虑了。 在学习C语言时,指针数据类型可以从一定程度上解决大结构参数传递所导致的低效问题,因为指针传递的是地址而非变量本身。 C++中的引用类型同样可以解决参数传递过程中的低效问题,而且在操作上比指针更方便,举例如下: 【例1-2】引用类型作为函数参数 intgetmax(int&a,int&b){returna>=b? a: b;} voidmain() { intx=10,y=20,z; z=getmax(x,y); } 注意函数getmax的两个形式参数a和b,都定义成引用的形式(而非指针。 指针定义是*),且在主函数中调用getmax函数时,实在参数就是x和y,并未采用其他的特殊形式。 采用引用参数后,函数调用时就没有参数传递的过程,没有用实在参数创建形式参数空间的过程。 形式参数&a被定义为实在参数x的别名,形式参数&b被定义为实在参数y的别名,函数体中的a就是实在参数x,b就是实在参数y。 很显然,引用参数带来一个副作用: 既然函数体中的a就是实在参数x,b就是实在参数y,那么在函数体中任何对“形式参数”a和b的修改都将直接影响到实在参数x和y,这在多数情况下是不允许的。 为了避免这种情况的发生,C++提供了const约束: 【例1-3】带有const的引用参数 intgetmax(constint&a,constint&b){returna>=b? a: b;} voidmain() { intx=10,y=20,z; z=getmax(x,y); } 这样以来,一旦函数体中出现针对a和b的修改,在编译层面上将会给出相应的错误提示。 1.4.2.3函数返回引用 通过对【例1-7】的分析得知,当函数定义有返回类型时,函数返回语句return将会创建一个返回值空间用来保存结果。 只有当返回值被有效使用或者丢弃后才会释放该临时空间。 与函数参数被设置为引用之后即可避免创建临时空间相似,如果一个函数返回一个引用,那么创建返回值空间的过程就可以避免。 但由于引用必须“粘”在一个已经存在的空间之上,所以函数返回引用必须对应于已经存在的空间。 【例1-1】函数返回引用 intvals[100]; int&put(intn){if(n>=0&&n<100)returnvals[n];elsereturnvals[0];} voidmain(){ put(0)=100; put(20)=50; cout< cout< } 当函数返回一个引用时,函数调用可以为赋值语句的左值。 函数体中定义的局部变量不能作为引用返回,因为局部变量在函数返回之前将被释放,从而使得引用无所“依靠”。 1.4.2.4引用的使用限制 引用类型的使用具有一定的限制,包括: 不能建立数组的引用,因为数组是一个由若干个元素所组成的集合,所以就无法建立一个数组的别名。 引用是对某一变量或目标对象的引用,它本身不是一种数据类型,因此引用本身不占存储单元,这样,就不能声明引用的引用,也不能定义引用的指针。 不能建立空指针的引用,如: int&rp=NULL;是错误的。 不能建立空类型void的引用,如: 不能建立void&ra=3;因为尽管在C++语言中有void数据类型,但没有任何一个变量或常量属于void类型。 1.4.3new/delete 在C++编程中,经常要用到动态存储空间分配和释放。 new和delete提供了更加灵活的申请和释放方式。 其实在C中也有类似的处理机制,与C++新增加的机制相比,使用难度较大。 在C中,动态申请100个整型数据存储空间的方式如下: ptr=(int*)malloc(100*sizeof(int)); 而在C++中可以使用更加简单的方式: ptr=newint[100]; 动态申请的存储空间需要及时释放。 这一点不仅对于C,而且对于C++都需要引起重视。 在C中的空间释放方式如: free(ptr); C++中的释放: delete[]ptr;//释放一个数组
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 面向 对象 程序设计 12