软件设计.docx
- 文档编号:18130066
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:19
- 大小:34.64KB
软件设计.docx
《软件设计.docx》由会员分享,可在线阅读,更多相关《软件设计.docx(19页珍藏版)》请在冰点文库上搜索。
软件设计
模块化程序设计与面向对象程序设计的比较分析
杨晋江
(西北师范大学文学院2003级汉语言乙班甘肃兰州730070)
[摘要]为了克服软件危机,人们开发出了面向对象程序设计与模块化程序设计,这两种设计理论都比较完美的解决开发过程中出现的问题,本文比较系统的介绍了这两种程序设计方法,通过阅读本论文可以基本了解两种程序设计方法的基本信息,可以对这两种方法有一个总体的印象。
[关键词]模块化函数变量编译预处理面对对象封装性继承性多态性
1模块化程序设计
1.1函数
1.1.1模块化程序设计基本思想
是将一个大的程序按功能进行分割成一些模块,使每一个模块都成为功能单一、结构清晰、接口简单、容易理解的小程序。
C语言提供如下一些支持模块化软件开发的功能:
⑴函数式的程序结构,每个函数都有各自独立的功能。
⑵允许使用不同存储类别的变量,为控制模块内部及外部的信息交换。
⑶具有编译预处理功能,为程序的调试、移植提供了方便。
1.1.2C程序结构
⑴用C语言设计程序的思路:
就是编写函数,至少要编一个main()函数。
⑵C语言程序的执行:
就是执行相应的main()函数。
即从它的main()函数的第一个花括号开始,依次执行后面的语句,直到最后的花括号为止。
其它函数只有在执行main()函数的过程中被调用时才能执行。
⑶函数的概念:
在高级语言中的“函数”实际上是“功能”的意思。
当需要完成某一个功能时,就用一个函数(可以是标准库中的函数或自己设计的函数)。
1.1.3函数定义与函数声明
⑴函数定义:
就是在程序中设定一个函数模块。
一个函数是由变量声明部分与可执行语句组成的独立实体,用以完成一指定的功能。
函数定义的内容:
①函数类型(即函数数值类型)。
通常把函数返回值的类型称为函数的类型。
②函数名。
函数名应符合C语言对标识符的规定。
③形式参数。
写在函数名后面的一对圆括号内
作用:
A、表示从主调函数中接收哪类型的信息;B、在函数体中形式参数可以被引用的,可以输入、输出、被赋以新值或者参与运算。
④函数体。
函数体是一个子程序结构,由变量定义部分和语句组成
⑤函数的返回
函数执行的最后一个操作是返回。
意义:
A、使流程返回主调函数,宣告函数的一次执行结束;B、将函数值送到调用表达式中。
注意:
函数不能嵌套定义,一个函数不能定义在其它函数体内部。
⑶函数声明
1一般格式为:
类型标识符函数名(类型标识符形参,类型标识符形参,…);
②注意:
可以不写形参名,但不能只写形参名而不写类型,也不能不写函数的类型。
1.1.4函数的传值调用
参数是函数调用时进行信息交换的载体;即实参是调用函数中的变量,形参是被调函数中的变量;调用过程中实现实参与形参的结合。
⑴特点:
①形参与实参各占一个独立的存储空间;
②形参的存储空间是函数被调用时才分配的;
③函数返回时,临时存储区也被撤销。
⑵总结:
函数中对形参变量的操作不会影响到调用函数中的实参变量,即形参值不能传回给实参。
传值方式:
函数只有一个入口----实参传值给形参。
一个出口----函数返回值。
1.1.5函数的嵌套调用
⑴函数的嵌套调用:
在调用一个函数的过程中又调用另一个函数,这种调用称为函数的嵌套调用。
⑵函数的递归调用
递归就是某一事物直接或间接地由自己组成。
分类:
直接递归调用;间接递归调用。
1.2变量的存储属性
1.2.1变量:
变量是对程序中数据的存储空间的抽象。
⑴变量有两种属性:
①操作属性(数据类型),
②存储属性。
⑵变量的存储器类型:
主存储器和寄存器。
⑶变量的生存期(存在性):
①永久存储:
在编译时分配的存储单元;
2动态存储:
只在程序执行的某一段时间存在。
3
⑷变量的可用域(可见性):
全局可用和局部可用。
⑸变量的存储属性:
动态变量、静态变量、外部变量。
1.2.2动态变量:
在程序执行的某一时刻被动态地建立并在另一时刻被动态撤销的一种变量,存在于程序的局部。
动态变量分类:
自动变量和寄存器变量
⑴自动变量
形式:
[auto]数据类型变量名[=初值表达式]
说明:
1自动变量是局部变量。
2在对自动变量赋值之前,它的值是不确定的。
⑵寄存器变量
具有与自动变量完全相同的性质。
当把一个变量指定为寄存器存储类别时,系统将它存放在CPU的一个寄存器中。
形式:
register数据类型变量名
1.2.3静态变量:
定义格式:
staic数据类型变量名[=初始化]
⑴在程序一开始执行便被建立,直到该程序执行结束都是存在的;
⑵静态变量的初始化是在编译时进行的;
⑶在函数多次被调用的过程中,静态局部变量的值具有可继承性;
⑷静态局部变量的值只能在本函数(或分程序)中使用。
1.2.4外部变量:
⑴外部变量是全局变量。
⑵外部变量使用时的几种情况。
①限定本文件的外部变量只在本文件使用。
②将外部变量的作用域在本文件范围内扩充。
③将外部变量的作用域扩充到其它文件。
⑶外部变量的副作用。
1.3编译预处理
1.3.1编译预处理:
是在编译前对源程序进行的一些预加工。
命令形式:
以#开头,末尾不加分号。
两个命令:
#define和#include
1.3.2宏替换:
定义:
用预处理命令#define指定的预处理。
⑴字符串宏替换
形式:
#define宏名宏体
#define标识符(形参表)宏体
如:
#definePI3.14159265
⑵带参的宏替换
宏不能递归地定义。
#definesquare(n)((n)*(n))
Main(){intI=1;
While(I<=10)
Printf(“%d\n”,square(I++));
}
1.3.3书写#define命令行应注意的几点:
⑴宏名与宏体之间应以空格分隔(宏名中不能含有空格)
⑵宏名不能用引号括起来。
⑶较长的定义在一行中写不下时,可在本行末尾使用反斜杠表示续行。
⑷带参宏定义,宏体及其各个形参应该用圆括号括起来。
⑸宏定义可以在源程序中的任何地方,但一定要写在程序中引用该宏之前。
⑹已定义的宏可以用命令#undef撤销。
1.3.4文件包含:
定义:
文件包含指通过命令#include把已经进入系统的另一个文件的整个内容嵌入进来。
⑴格式1:
#include“文件标识”
格式1中:
①文件标识中可以包含文件路径;
②搜索方式:
首先在源文件所在目录查找;然后按系统。
⑵格式2:
#include<文件标识>
格式2中:
只按系统指定的标准方式查找文件目录。
注意:
记住常见的头文件及其功能。
2面对对象程序设计
2.1面向对象的程序设计
基于面向对象方法学原理的程序设计,英文简称OOP。
2.1.1基本概念
认识一个系统的过程和方法同用于分析、设计和实现一个系统的过程和方法很不一致。
对一个系统的认识是一个渐进过程,是在继承了以往的有关知识的基础上、多次迭代往复而逐步深化的。
在这种认识的深化过程中,既包括从一般到特殊的演绎,也包括从特殊到一般的归纳。
而以往用于分析、设计和实现一个系统的过程和方法大部分是瀑布型的,即后一步是实现前一步所提出的需求,或者是进一步发展前一步所得出的结果 。
因此,当越接近系统设计(或实现)的后期时,如要对系统设计(或实现)的前期的结果作修改就越加困难。
同时也只有在系统设计的后期才能发现在前期所铸成的一些差错。
当这个系统越大、问题越复杂时,由于这种对系统的认识过程和对系统的设计(或实现)过程不一致所引起的困扰也就越大。
为了解决这种不合理的现象,就应使分析、设计和实现一个系统的方法尽可能地接近认识一个系统的方法;换言之,就是应使描述问题的问题空间和解决问题的方法空间在结构上尽可能地一致,也就是使分析、设计和实现系统的方法学原理与认识客观世界的过程尽可能地一致。
这就是面向对象技术的方法学的出发点和所追求的基本原则。
和人们认识世界的规律一样,面向对象技术的基本方法学认为:
客观世界是由许多各种各样的对象所组成,每种对象都有各自的内部状态和运动规律,不同对象间的相互作用和联系就构成各种不同的系统,构成客观世界。
当设计和实现一个客观系统时,如能在满足需求的条件下把系统设计成是由一些不可变的(相对固定的)部分所组成的最小集合,则这个设计就是优秀的,而这些不可变的(相对固定的)部分就被看成是一些不同的对象。
2.1.2基本特征
基于面向对象方法学原理的程序设计,至少应当具有以下的特征:
⑴ 模块性。
一个对象是一个可以独立存在的实体模块。
从外部来看这个模块 ,只了解这个模块具有哪些功能,至于这个模块的内部状态,以及如何实现这些功能的细节都是“隐蔽”在模块内部的。
一个模块的内部状态是不受(或很少受)外界影响的。
同时,一个模块内部状态的改变也不会影响到其他模块的内部状态。
因此,各模块间的依赖性很小,从而它们才有可能较为独立地为各个系统所选用。
⑵继承性和类比性。
人们是通过对客观世界中的各种对象进行分类及合并来认识世界的,每个具体的对象都是在它所属的某一类对象(类)的层次结构中占据一定的位置。
因此,下一层次的对象应具有上一层次对象的某些属性,在面向对象技术的方法学中,把它称为是下一层次的对象继承了上一层次的对象的某些属性。
另一方面,当发现一些不同的对象具有某些相同的属性时,也常常把它们归并成一类,在面向对象技术方法学中,把它称为是通过对象间的类比而实现了归类。
⑶动态连接性。
在客观世界中,由于存在各式各样的对象以及它们之间的相互连接和作用,从而构成了各种不同的系统。
因此,把对象和对象间所具有的一种统一、方便和动态地连接和传递消息的能力和机制 称为动态连接性。
⑷易维护性。
任何一个对象都是把如何实现本对象功能的细节隐藏在该对象的内部。
因此,无论是完善本对象的功能,还是改正功能实现的细节,都被划归于该对象的内部,而不会传播给外部,这就增强了对于对象和整个系统的易维护性。
2.1.3基本方法
传统的结构化的程序设计方法是面向数据的,它是将大量复杂的数据结构分离成一些独立的、彼此无关的简单的数据结构。
将这些简单的数据结构映射成小程序,再用小程序组装成应用软件。
基于面向对象的方法学原理进行程序设计时,是按照以下的方法进行的:
⑴确定构成该系统的各个组成部分(即对象)及它们的属性。
⑵确定每一组成部分(即对象)应完成的功能。
⑶建立每一组成部分(即对象)与其他组成部分(也是对象)的相互关系。
⑷建立各个组成部分(即对象)间的通信关系和接口形式。
⑸进一步协调和优化各个组成部分的性能及相互间的关系,使得该系统成为是由不同的组成部分(即不同的对象)的最小集合所组成的。
⑹分析、设计及实现每个组成部分(即对象)的功能实现细节。
[1]
面向对象的程序设计是对问题域进行自然分割,将问题的数据及其行为功能结合一体 。
对 象直接 对应于软件结构。
对象统一了数据和处理,对象间的通信(消息)统一了数据流和控制流。
程序的执行就是对象间的消息传送。
2.1.4程序设计语言
已设计出多种面向对象的程序设计语言,如基于对象的语言ADA(在ADA中,程序包就是对象 );基于类的语言CLU(在CLU中,Cluster实际上就是类 );和面向对象的语言 Smalltalk 、C++等。
各种语言之间在封装、数据抽象、继承性、连接方式、存储管理、操作符重载、软插件库、并行性等方面有相同和不同之处。
[2]
2.1.5程序设计环境
在面向对象的开发方法中,软件系统是由对象组成的,而对象则是一个能完整地反映现实问题本质的实体。
面向对象的程序设计方法使程序人员摆脱了具体的数据格式与过程,而可集中精力去研究所要处理的对象。
由于数据抽象、信息隐蔽机制使得对象的内部实现与外界相独立,从而构成了一个可重用的软件成分,即使对对象内部做修改,也不会引起外部(即系统)的变化。
这种方法在创建和组合可重用软件成分上也有很大的灵活性,它可通过继承已有的对象性质(数据和操作)产生新的对象。
几乎所有面向对象的程序设计环境都配有很丰富实用的基本类库(有些还配有面向对象的数据库),这对提高系统软件和用户软件的可重用性发挥了极大的作用。
2.2封装性继承性多态性
面向对象的程序的最根本的目的就是使程序员更好的理解和管理庞大而复杂的程序,它在结构化程序设计的基础上完成进一步的抽象。
这种在设计方法上更高层次的抽象正是为了适应目前软件开发的特点。
面向对象的程序设计中最基本的概念是对象,一般意义上的对象指的是一个实体的实例,在这个实体中包括了特定的数据和对这些数据进行操作的函数。
对于面向对象的程序设计,一个对象具有状态(state)、行为(behavior)和标识(identity)。
对象的状态包括它的属性和这些属性的当前值。
对象的行为包括可以进行的操作以及所伴随的状态的变化。
对象的标识用来区别于其它的对象。
而一个COM对象的行为由它所支持的接口来定义。
我们通常不显式的指定COM对象的状态,而被其接口所包含。
使用IUnknown:
:
QueryInterface在接口之间进行移动的能力定义了COM对象的标识。
对象的核心概念就是通常所说的“封装性”(encapsulation)、“继承性”(inheritance)和“多态性”(polymorphism),下面我们分别阐述其具体含义。
2.2.1封装
按照面向对象编程原定义,所谓的封装性是指隐藏类(class)为支持和实施抽象所作的内部工作的过程。
类的接口是公有的,它定义了一个类所能完成的功能,而这些接口的实现是私有的或受保护的,它定义了类完成这些功能所作的具体操作。
对于使用这些类的编程者来说,只需要知道类所能完成的功能,而不需要知道这些功能具体是如何实现。
拿我们所常的手表作为例子,在使用手表时,我们只需要知道手表所能完成的功能和如何使用手表来完成这些功能,这些内容相当于对象的接口。
我们不需要知道在手表的内部,这些功能是如何实现的,因此,对于手表来说,无论手表使用的是一般的机械摆,还是石英振荡器,只要它们的使用方法是完全一样的,用户就没有必要知道这个不同。
另一个例子的集成电路芯片,我们只需要知道该芯片的每一个引脚的电气参数和功能,而不必知道这些功能在芯片的内部是如何实现的,就可以使用该芯片来组装电路。
如果仅就封装性而言,这里的手表和集成电路芯片就相当于面向对象的编程中的对象。
下面我们再举一个例子,众所周知,Win32操作平台目前包括Windows95和WindowsNT,而事实上,Windows95和WindowsNT使用了完全不同的内核,对于很多同样的操作,在Windows95和WindowsNT两个操作系统下的实现方式是不同,但两个操作系统都提供了同样的Win32应用程序编程接口(API),这样,我们所编写的应用程序只需要和Win32应用程序编程接口打交道,而没有必要知道具体的每一个Win32API函数在操作系统中是如何实现的。
换一种说法就是,Windows95和WindowsNT封装了Win32API函数的具体实现。
一个类在定义数据的同时也定义了对这些数据的操作,这些操作称作方法(method)。
按照面向对象的定义,方法就是对对象中的数据的访问。
在C++中,对对象中的数据的访问是通过公有成员函数来进行的,这些公有成员函数可以在对象的外部进行调用,它们提供了对象的外部接口。
而对于这些接口的内部的实现在对象的外部的不可见的,这些实现包括了类内部所使用的数据结构和支持公有方法的实现的私有成员函数,通常,这些数据成员和成员函数是私有的,它们只能为类中成员函数所访问,而不能从类的外部进行访问。
访问一个方法的过程称为向这个对象发送一个消息(message),对象的工作是靠消息来激发的,对象之间也是通过消息发生联系的,即请求其它对象做什么或响应其它对象的请求是通过发送或接收消息来实现的。
注意:
这里的所说消息和在Windows编程中所常说的消息是两个不同术语,尽管在某些方面两者的确有很多相象之处。
封装可避免许多维护性问题。
如果一个基本数据类型的结构被修改了,例如一个链表修改成了一个数组,除类中的访问该数据的方法的代码外,软件系统的其余部分是不受影响的,因为基本数据在外部是不可见的,只能通过公有方法的接口与基本数据发生联系,改变一个类的实现,丝毫不影响使用这个类的程序员,从而大大的减少了应用程序出错的可能性。
[3]
2.2.2继承
类支持层次机制,因此我们可以借用可重用性部件来很容易的从一个或多个已有类出发,来生产各种更符合我们要求的新类。
假设我们从类A出发来派生新的类B,那么我们称类A为类B的基类(baseclass),类B为类A的派生类(derivedclass),类B继承了类A中的各种行为和状态,并可添加自己的成员变量和成员函数。
图1自然世界中的继承关系
我们先来看一个例子,图1给出了自然世界中的生物的一种继承层次图,最高层次的生物类代表了层次结构中最一般的概念,较低层次的类表示由上一层的类(即其基类)所派生的特殊的概念。
如上面的继承关系,动物类从其基类—生物类中继承生物类的所有属性和行为,并且定义了动物类所特有的属性和行为,类似的,脊椎动物类从动物类那儿继承了所有的属性和行为,并且定义了自身特有的属性和行为;……,人类从灵长动物类那儿继承了所有的属性和行为,并且定义了人类所特有的属性和行为。
之所以举上面的例子是为了将程序空间和现实生活空间来进行对比,结果说明一点,类的继承使得我们可以以一种自然的方式来模拟生活空间中的对象的层次结构,也就是说,我们可以以一种符合正常思维逻辑的自然的方式来思考和组织应用程序的结构,然后,可以将这个结构几乎不作修改或者只需作少量的修改地用面向对象的编程来表达,从而大大的缩短了软件系统的开发周期。
图2类CEdit在MFC中的继承层次
下面我们举一个现实编程中的例子,考虑MFC(MicrosoftFoundationClassLibrary,Microsoft基础类库)中的CEdit类,它封装了Windows中的编辑框控件,图2显示了CEdit类的继承结构。
在图2中,类CObject是所有的MFC类的根(根是一个术语,它指在继承层次中处于最顶层的类,根是所有继承层次中的类的最终基类),在类CObject中提供了功能有:
串行化支持、运行库信息、对象诊断输出以及与集合类的兼容等。
类CCmdTarget从类CObject直接派生,它是Microsoft基础类库的消息映射结构的基类,消息映射将命令和消息传递给所编写的处理成员函数,这里,命令指来自菜单项、命令按钮和加速键的消息。
类CWnd提供了MFC中所有窗口类的基本功能性,它封装了Windows中的窗口句柄hWnd。
类CEdit从CWnd直接派生,它提供了对Windows编辑控件的特定支持。
我们看到,类CEdit本身仅提供了特定于编辑控件的38个成员函数,但是,你可以通过类CEdit进行调用的成员函数却多达300多个,事实上,这些成员函数中的绝大部分由其基类所提供,其中CWnd就为CEdit提供了多达304个成员函数,由于CEdit类继承了其基类的数据和方法,因此,可以通过CEdit类调用CWnd类中提供的方法来实现对标准Windows窗口的操作。
继承机制所带来的最大优势在于使软件系统非常的易于扩充,程序员不仅可以直接的使用各种已有的类,还可以从这些类方便的派生出新的类,新的类继承了基类所包括的所有接口和功能,因此只需要定义和实现与基类所提供的功能中不同的那一部分,这大大的降低了软件开发的复杂性和费用,因此面向对象的编程方式非常之适合于进行大型软件系统的开发。
降低软件开发的复杂性的意义不仅在于它可以有效的降低软件开发的费用,而且还使得在软件系统中出错的可能性大为减少。
由于类有着清晰的继承层次,因此,我们可以很快的定义出错的代码所处的位置,因此能够很快的修正程序中出现的问题。
继承机制还使得我们可以将与现实生活空间相一致的思维方式应用于程序空间,即我们可以在程序设计时使用直观的思维方式设计程序中所使用的对象的层次结构,然后,直接将此结构映射到面向对象的程序空间,而不需要做任何修改或仅需要作少量修改就可以使用面向对象的程序设计方法来实现该结构。
这时,编写程序的过程更类似于“搭积木”,我们可以从很多途径来获得到程序所需使用的各种对象,然后,将这些对象以一定的层次结构组合起来,从而实现程序的逻辑结构。
[4]
2.2.3多态和虚函数
在讲述多态之前我们先来看一个问题。
仍以前面的图2为例,假定我们已经定义了一个指向哺乳动物类的实例对象的指针,如下所示:
CMammal*pMammal
然后我们定义了一个人类的实例对象和一个狒狒类的实例对象,如下所示:
CHumanHuman;
CBaboonBaboon;
然后,我们可以将指针pMammal指向Human对象,在C++语言中是可以这样做的:
pMammal=&Human;
也可以将指针指向Human对象:
pMammal=&Baboon;
考查上面的两种情况,我们假定在哺乳动物类、人类和狒狒类中都定义了一个Eat(吃)方法,很显然,当我们使用下面的代码来调用人类对象和狒狒类对象的Eat方法时不会遇到什么问题:
Human.Eat();
Baboon.Eat();
但是,现在来考虑下面的代码:
pMammal->Eat();
当pMammal指向不同的对象时,上面的代码将发生什么样的结果。
很显然,当pMammal指向一个哺乳动物类的实例对象时,上面的代码将调用哺乳动物类的Eat方法。
但是,当pMammal指向一个人类的实例对象时,上面的代码是调用人类的Eat方法呢,还是仍然调用哺乳动物类的Eat方法?
我们期望的是前面一种情况,这就是类和对象的多态性。
我们期望,当pMammal指向不同的实例对象时,编译器将根据实例对象的类型调用正确的Eat方法。
在C++中,类的多态性是通过虚函数来实现的。
就上面的例子来说,我们将Eat方法定义为一个公有的虚函数,这样,当pMammal指针指向一个人类的实例对象时,编译器调用的就是人类的Eat方法,当pMammal指针指向一个狒狒类的实例对象时,编译器调用的就是狒狒类的实例对象,从而实现了运行时的多态。
在C++中,多态定义为不同函数的同一接口。
从这个定义出发,函数和操作符的重载也属于多态。
考虑下面定义的两个函数:
intprint(char*);
intprint(int);
上面的两个不同函数使用同样的函数名,在C++中,这称为函数的重载。
这时,若将一个字符指针传递给print函数,如下所示:
char*sz="HelloWorld!
";
print(sz);
这时,编译器调用的是intprint(char*),如果将一个整型变量传递给print函数,如下所示:
inti=0;
print(i);
则编译器调用的是intprint(int)。
这种根据所传递的参数的不同而调用不同函数的情形也称作多态。
下面我们来看运算符重载的例子,在下面的过程中,我们为矩阵类重载了运算符“+”:
CMatrixoperator+(CMatrix,CMatix);
然后使用下面的代码:
inta=1,b=1,c;
CMatrixA(3,3,0),B(3,3,1),C(3,3);
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 软件设计