• 104.00 KB
  • 2022-04-22 11:19:50 发布

软件技术基础 第三版 课后习题答案.doc

  • 14页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档共5页,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,可选择认领,认领后既往收益都归您。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细先通过免费阅读内容等途径辨别内容交易风险。如存在严重挂羊头卖狗肉之情形,可联系本站下载客服投诉处理。
  4. 文档侵权举报电话:19940600175。
'第二章程序设计语言计算机工作是执行相应程序,程序规定了执行的动作和动作的执行顺序。程序的表达手段是程序设计语言。程序设计语言是人-机交换信息的媒体;是表达软件(程序)的工具;是人-人交换信息的工具。软件的开发和使用,协作开发、使用修改都要读程序,程序设计语言必须规范化和标准化。程序设计语言是与计算机通信创造的语言,严格、小巧,没有二义性(语句执行只有一个解释)。2.1高级程序设计语言概述最初的语言是机器语言。机器语言在内存中开辟两个区:数据区存放数据;指令区存放指令。CPU从指令区第一个地址开始逐条取出指令并释义执行,直到所有的指令都被执行完。一般的指令格式2.2.2高级语言程序的解释执行编译型语言由于可进行优化(有的编译器可作多次优化),目标码效率很高,是目前软件实现的主要方式。语言编写的源程序,都需要进行编译、连接,才能生成可执行程序。编译时花费时间但程序的执行效率提高。对高级语言源程序采取解释执行的方式.解释执行需要有一个解释器(Interpreter),它将源代码逐句读入。先作词法分析,建立内部符号表;再作语法和语义分析,即以中间码建立语法树,并作类型检查。完成检查后把每一语句压入执行堆栈,压入后立即解释执行。操作系统的命令、BASIC、VB、Prolog、LISP、Java、JavaScript、Postscript2.3.1变量、表达式、赋值使用符号常量,只需一次性改动其赋值就行了。赋值和函数调用是程序语言改变变量的值的基本手段。不同的语言所使用的赋值号并不完全相同,比如Pascal语言的赋值号是“:=”,而在C语言、Java语言、VB等语言中,赋值号是“=”。程序中的一条语句对应着计算机的一条命令(用一条或多条指令来实现)。一个赋值语句就是一条赋值命令。2.3.2程序的控制结构程序约定自上向下自左向右地执行,即顺序地执行语句(或表达式)。但不仅限于此,计算机之所以能自动计算,它能通过判断将程序转到应该执行的地方。人们就是通过巧妙地安排控制转移,使计算机实施算法。 最基本的程序控制语句在汇编语言时代就有了Jump指令使执行跳转,对应的高级语言语句是无条件转移语句:gotoloop;其中loop是跳转到的语句的标号(数字或标识符),加上条件判断子句if(E)就是条件转移语句:If(E)gotoloop;其中E是条件(布尔)表达式,求值结果是‘真’、‘假’值。如果为‘真’转移到标号为l的语句,为‘假’则按顺序执行下一条语句。有了这两个语句再加上简单语句(赋值、调用、输入/出),就可以实现程序的任何执行控制。最基本的程序控制有以下三种:顺序执行简单语句序列S=S1;S2;…;Sn选择执行结构化程序的控制结构早期的编程语言是语句级的.用简单准语句集合加goto构成复杂的程序控制。然而显式地使用goto语句是极其危险的。这是因为:·goto语句相互交织使设计出的程序控制结构成为不可分解的一个整体。尽管算法设计精巧,但牵一发而动全身。程序一大,修改很困难。·显式使用goto语句使得任何写错转移语句标号的小错误都会导致灭项之灾。·使程序控制逻辑是结构化的,显式使用goto语句,程序依然难于阅读。出了错易于找出错误并修改易阅读导致易扩充、修改,大程序易于分析。程序控制结构清晰,是它用关键字控制程序块(语句组)。任何控制转移不能进入这些控制块,除非入口。块中转出也不能直接转到程序其他处,只能转到出口。在出/入口增加检查语句就使得程序错误真正局部化。程序块级(语句组)控制采用语句括号使程序逻辑与表示法结构完全一致。可直接编程。程序语言结构化以后,编程对流程图的依赖就很少了。Nassi-Schneldermann提出了结构化流程图(主要取消流线及箭头),因没有直接用类似结构化编程语言的伪代码方便而没有流行起来。结构化编程语言的其他控制结构结构化程序只需三种基本结构重复和嵌套。但为了方便编程,第三代语言派生出许多结构。以下简略说明。条件分支无假块条件分支和嵌套if语句if(E)thenSTendif if(E1)thenS1elseifE2thenS2…elseSmendif无假块条件语句是正规条件语句的简化,很常用。嵌套语句在否定部分用关键字elseif,有的语言用elsif。只有当所有条件均有‘假’时才执行else块。逐个检查m个条件效率很低,有时似无必要。case语句也叫分情形语句。根据条件变量Z的值单独执行S1,S2…,Sn,执行完Si自动跳到endcase(C语言例外,它要加上break才跳。否则执行Si+1到Sn)。循环结构除了doSwhileE之外,还有do-until和for结构。do-until语句形式是doSuntilE,基流程图。先执行再判断,若E为‘真’不再循环。正好和先判断再执行的do-while语句是相反的。将它改成do-while结构十分容易。条件取反,先执行一次S块。for-do语句以控制变量增减值或枚举集合值计数的循环。for-do是do-while的另一种变体结构,其书写格式有多种变体。do-while-do语句其形式是doS1whileEdoS2enddo流程图。把与条件无关的部分或影响E值计算的部分作无限循环。E为‘真’才作S2,否则执行enddo后面的语句。2.3.3数据类型计算机中计算对象(不管是常量、变量)都是有类型的,不能把一个实数和逻辑‘真’值(true)相加。指出数据类型的一个目的就是要避免这类错误的发生。各语言转换函数(inttoreal或realtiont)不尽相同,有的是把类型名作为转换函数名,不兼容的类型,如整数和布尔一般不能转换,但C语言例外。数组类型变量代表单个数据值叫纯量变量。如果代表多个(数组)或多种(记录)值就叫它结构型数据的变量。先来说数组:以下声明可将其连接BigArray(0)=Names()//第1元素放整个数组bigArray(1)=ages()//第2元素也放整个数组使用时用二维索引BigArray(0,7)=Names(7)//=不是VB语言符号BigArray(1,7)=Ages(7)记录类型引入记录数据类型:相同或不同类型数据组成的结构叫记录.记录型记录了对象的属性信息,记录的各个组成部分,称为记录域,各个域的数据类型可以不相同。下面给出用VB声明的一个例子:TypePersonRecordNameAsString AgeAsIntegerSexAsStringTelnumberAsStringLableAsIntegerEndType类型定义中列出了属性名和属性的数据类型。记录类型变量的一组值(或一行值)称作元组(tuples),由属性变量的值组成。每个属性变量指明了一个(取值)域.也叫字段。有了这个类型就可以声明记录型变量。该变量在运算中只能按域取值,域的表示法是变量后接符号“.”再接域(属性)名。指针类型指针类型是一种很重要的数据类型,但同时也是一种简单却不大好理解的数据类型。在讲述指针之前,首先来看一个例子(采用C语言来描述):IntI;//定义一个整型变量II=1;//i赋值为1I=i+1;//表达式计算在上面的例子中,首先定义一个整数型变量,然后设该变量的值为1。在第三行使用了一个赋值语句:i=i+1。现在要问,i是一个数学意义上的变量吗?如果是的话,i=i+1这个等式显然不能成立!所以,计算机语言中的变量并不是数学意义上的变量,它代表的只是计算机内存中的一个存储单元。这个单元中存放的内容是变化的,所以把它叫做变量。每个变量都有一个名字(标识符),对应为存储单元的地址,存储单元的内容为变量的值。引用变量的值时直接把变量名放到用值的地方(赋值语句的两边)。程序运行时按地址存取内容。如果某个变量的值是内存中的地址.这个变量叫指针变量。C语言中指针类型如同数组类型,以类型指明符‘*’表示。它所指向的对象是有类型的。Inti*p;//声明i为整型,P为整型对象的指针charc,*pCh;//声明c为字符型,pCh为指向字符里对象指针‘*’放在变量标识符之前,出现在类型标识符之后,指明它所修饰的标识符是指向该类型对象的指针.p是指向整型的指针,*p是p所指向的无名变量的代名词.声明完成后变量p有存储单元但无内容.同样,类型说明符‘&’说明它所修饰的标识符为引用类型的变量。int1.&r=i;2.3.4过程例如要三次求正弦值,不是连续写三个求正弦值的程序,而是把这段程序从主程序分离开来,简称过程,可以多次调用。分离出来的部分叫子例程(routine),执行完后依然返回原处。带返回位的叫函数过程,不带返回值的叫子例程过程.简称子程序。函数的自变量(参数)为x,并指出其函数(返回)值是Double 类型.每当程序中出现求正弦时,直接调用该过程,称函数引用,即引用该函数的(返回)值。一个主程序中写三段相似代码的执行情况是一样的,只是省写了两次,多了三次实参和形参匹配(置换)的执行过程。函数过程是参数化(更抽象)的程序。使用过程还可以降低程序复杂性、使程序结构变得消晰明了。假设有一个主程序引用了30个函数,每个函数引用了100次,如果在每个引用函数的地方都要写出函数的代码的话,程序将变得十分繁杂。如果使用函数,就显得简明清晰。还可以提高编程效率。许多可以标准化的函数,可以事先编写调试好,放到函数库中,使用时直接调用即可。过程的定义从过程关键字开始到过程结束之间的一段封闭的程序就是过程定义,它由型构(signature)和过程体(body)组成,以下是两种过程定义的结构:FunctionFname(形式参数表)AS返回类型‘本行为函数型构’[类型和数据声明]‘以下是函数体’语句集EndFuncsub名字《形式参数表》‘本行是子程序型构’[类型和数据声明]‘以下是子程序体语句集EndSub以上可以看到除了关键字和形参表外过程和一般的程序没什么两样。函数过程型构只比子程序型构多一个返回类型。有的语言规定函数名必须在函数体中至少在赋值号左边出现一次,以便带回返回值,如VB。有的语言可指定任何返回表达式,如C。主程序中的数据通过型构的形参表进入过程。在过程中也可以声明数据,不过这类数据与主程序没什么关系,主程序中无法访问它们,它们是局部变量。局部变量的有效范围,即作用域(scope)仅限于本过程。从主程序一侧看,这些局部变量都被过程隐藏起来了,只有型构中的参数表是通向外部的窗口(在此交换数据),所以型构也叫接口(Interface).为与一般变量区别把形参表中的变量称为变元(Argument)。请看下面的两个例子过程调用函数过程因返回位可以出现在主程序的表达式中,以函数名引用函数值。并列出与形参表变元的个数、类型、次序一样的实在参数表.如Fahrenheit=Degress(42.0)//函数引用作为表达式子程序过程的使用是过程调用,它相当于浓缩的一段程序。PrintNStar5//调用过程,打印五个星号过程调用是把过程体的代码调回到主程序的环境下执行。形参和实参变元匹配之后过程运行,和主程序中其他代码运行没什么两样.主程序中声明的变量在过程中自动可用。全程变量和局部变量,局部变量在过程执行完之后所有的数据和过程体都消失了,因此出了过程体再引用过程中的变量当然出错。过程调用这种执行机制,为程序运行、节省内存空间带来了极大的好处。程序中的数据对象,自它声明时起到本程序块结束(见到End)为其有效的作用范围(作用域)。显然,内块嵌入在外块内,外块的变量,内块中是能见到的,这就可能导致这样一个问题:外块的变量与内块同名怎么办?程序设计语言中以“就近声明优先”准则来处理。内块程序中所有出现X的地方均按布尔类型解释,Double型的X虽在其作用域内但被覆盖(overrided) 了,通过点表示法才能出现在内块中:(P.X>5.0)andX指明头一个X是P中声明的X,是Double类型的。后一个X是本块Boolean类型的。2.3.5过程的数据传递过程是一段封闭的程序,型构是它与外界通信的接口,它的变量是抽象的,通过形、实参数匹配具体执行。执行出口是本过程末端(除非中间有显式的retun语句).数据出入有两个途径,一为全程量,一为参数表。上小节己说过全程量,此处讨论参数表。无参过程2.3.6变量的生命期和Static变量程序中变量生命期随其声明所在程序块而异。程序一执行完它的所有数据变量均失去意义,它所在的存储区则可另行分配其他程序装入执行。因此,主程序中变量的生命期比过程中变量生命期要长,内嵌的子过程其变量生命期更短,最长的是文件变量,它放入磁盘,程序没有了它依然存在,所以按“生命期”的长短有:持久变量除非人为销毁全局变量出了程序便失去定义静态局部变量见下文自动变量(局部变量)出了所在块便失去定义循环控制变量出了循环便失去定义常常有一种需要,一个局部变量在它所在的局部程序块消失后依然保留其值,又不希望它是全局变量被该局部块以外的程序引用。例如伪随机数发生器,为使伪随机数每次调用新值,则上次数是生成下次数的种子,如不希望其他操作影响这个发生器,此时用Static(静态)变量更方便:2.3.7输入/输出程序总得把计算的结果告诉使用者,输出一个可识别的结果。非简单计算情况下还得在运行时获得用户的输入。程序的输入/输出分作两大类:一类是程序之间以文件形式进行数据传递;另一类是人一机交互,把人们可识别的形式(字符串、数)按一定格式输入到程序变量中。输出则相反,按用户要求的格式显示或打印。这一般由高级程序设计语言以过程调用(标准过程)的形式实现。过程在高级语言内部通过调用操作系统的系统调用完成。输出:DF,CF=__35.2272410___0.0000000C语言的输出语句功能很强且具有计算机功能;输出变量处直接写表达式,可以输出其结果值。例如:Printf(“DF,CF=%12.7f%12.7fn”,a+b*df,3.0/7.0)在VB中,可以利用MsgBox函数创建一消息对话框,向用户反馈程序的输出信息。2.4面向对象程序设计语言的基本特征2.4.1对象概述如图2.15所示,设项有一个程序有100个子程序,经过分析,这100个子程序并不是每个子程序都要用到所有的数据,把数据相关和程序相关(有嵌套调用)的分成组。例如,可以看到Sub1~Sub22加工第一组数据、Sub23~Sub57加工第2组数据……一个大程序分成五个大模块,只留过程接口等待外面调用。当然,模块间也可以彼此调用,例如图2.16中,Sub22调用Sub89、Sub43调用Sub62 等。尽管数据可沿着调用路线来回串,但加工最后结果都落实在各模块声明的数据存储单元内,总结果若在第二、四模块内,下一道打印命令(也是调用),它就把自己那组数打印出来。进一步分析发现这些大模块的数据和操作往往是描述客观世界中的一个对象,例如一个堆栈、一台打印机、一个雇员、一个窗口……拿数据堆栈来说,堆栈体(由数组或队列类型实现)、入栈的数据和栈高指示(变量)就是堆栈的数据,压入(Push)、弹出(Pop)就是它的基本操作,询问是否空(IsEmpty)是否满(IsFull)是它的辅助操作。这样封装的程序块就是一个复杂的计算对象,私有的数据描述了本对象的状态(如数据堆栈的情况);操作表示了本对象的行为(能接受询问IsEmpty、IsFull,会压栈Push,会弹栈Pop):对象接受外界的消息而动作,其结果是改变了对象内部的状态(数据在栈中出入)。请参看图2.18所示。这样封装之后使用者就不必关心对象内部情况,只按接口规定的形式向它发消息(如同调用)就可以了。如对象的设计者发现某个操作的算法(过程体)不好,重新改写一个,使用者也不用知道,只要接口形式不变就行。过去只有数据类型及数据结构、过程(算法描述)和嵌套过程,把一个活生生的世界硬拆成过程式程序表达,使用者要知道许多内部细节,设计者调试起来也极不方便。程序对象提供了直接描述客观世界对象的有力手段.数据叫做对象的属性(Attribute);操作则改称方法(Method),即改变属性的方法。对象间相互只有通信,调用方法叫发消息(Message)。消息——方法与过程调用——过程体的定义几乎完全一样,但意义不同。过程式语言在过程调用时主程序等待直至过程返回:消息则不一样,因对象是自主的程序实体,发消息者可等可不等,接受消息的对象可以立即响应可以稍晚些时间响应,这降低了对象间的引用耦合,为并发程序、事件驱动程序提供了程序实现的技术基础。2.4.2类与对象对象是封装了属性和方法的实体,客观世界的对象往往有许多相似之处,例如,一个班上40个人,就学生而言他们的属性和方法完全一样,只是姓名、年龄不一样,交作业的内容不一样。沿用程序中表达数据的抽象办法:定义一个类型、声明该类型的变量,定义一类对象然后声明它的不同实例(Instance):DimvAsDouble//v是双精型的数据变量DimOKAsCommandButton//OK是按钮类的对象变量这个类CommandButton是系统事先定义好的,OK是它的实例对象。OK有着和CommandButton一样的属性和方法,只是要给出各属性的值。类是生成实例的样板,是实例加工厂(给出一组属性值就是一实例)。正是因为变量一类型和实例一类的相似性,许多语言(如C++)都把类看作是类型,类的定义如同以简单的类型构造复杂的数据类型一样,只不过类定义时还要定义类的方法。类方法可以只写型构.方法体可以在类之外定义,以下是C++的字符堆栈类:这个程序的图示如图2.18。C++的main()函数相当于实例对象,Java已将它改成主程序对象的方法(外面套上对象名)。当声明:Char_stackstk1(100);消息和方法消息(message)相当于过程语言的过程调用,可带实在参数:方法(method)则相当于过程定义,带参数也是形式参数,一定要有方法体(执行语句集).面向对象中只有消息一方法,没有过程调用一过程体的说法。方法体中又可以向其他对象发消息,类似于本例的main向char_stack、stkl、stk2发消息。这样一个应用程序就是多个对象之间发消息。类对象接受生成、撤消实例对象的消息,这些方法叫类方法。相应用到的变量叫类变量。类定义中的其他方法和数据变量叫实例方法和实例变量。实例变量如上所述,每生成一个实例对象就要复制一份。实例方法只有一份,所以面向对象程序运行前要先装入类。 类与类型类是由简单类型组成的复杂类型,和用户定义的复杂类型即学术上叫抽象数据类型(ADT)有相似之处:·类型名封装数据和操作·数据由若干基本类型或已实现的复杂类型定义的变量组成·操作是加工这些数据的子例程和函数·有外部可见性控制public(公有)、Private(私有)和类型不同之处:·类型定义实例变量参与程序运行,类型仅是对变量的计算性态的描述.不参与运行。类也是程序对象参与程序运行。·子类型是类型的真子集,不是操作减少就是数据取值范围缩小,子类是类的例化.它增加数据和操作,使对象更明确。从复杂数据类型到类是一个飞跃,虽然底层的计算仍然是基本数据类型和子例程,但换了个说法,把类型数据叫属性集,子例程叫方法,过程调用叫发消息,就便面向对象程序和过程大不相同。过程式程序的执行顺序,尽管是并发程序,都是设计者事先考虑好了的执行过程。面向对象程序由于对象相对独立可以支持事件驱动程序。一个窗口上有按钮、菜单、图标、图符,用户可以任意点击一个都能运行,其先后没有约定,用过程式语言就要编一个轮循响应程序,依次问用户点击了谁,如果增加了按钮或删除某个图符,轮循程序就要改,否则无法运行。面向对象程序天然支持这类应用其示意图如图2.19所示:对象A、B、C都是独立的,联线是表示运行时发消息的路径,不运行是没有的。空心箭头是触发的消息(例如鼠标点击),图示给出两个,谁先谁后都没有关系,增加或减少一个对象或方法,只对用到它的对象稍作修改,不需要更改总控程序。2.4.3类定义面向对象编程,主要学会定义类。VB的所谓面向对象编程是不彻底的,它的控件都是对象,但其类定义由系统做,用户只能在它提供的属性和方法的前提下.生成并使用实例对象,用户全无类定义概念。VB.Net回到面向对象语言的大家庭,才提供类定义机制,所以本节只能用C++举例。类定义以类名封装类成员,其中分数据成员和方法成员。只有指明它们是公有的(Public),其他类的对象才可以访问,私有(Private)成员类内成员均可访问。数据和操作全部公有失去封装和数据隐藏的意义.全部私有只是一个孤立的对象,也失去对象通信模拟客观世界对象的意义.所以,一般是数据成员全部私有,方法成员多半公有、少量私有。私有方法只接受公有方法的消息,间接为类外对象服务.2.4.2节char_stack类的例子就是很好的类定义,该定义中没有私有方法。构造子和析构子类是对象的制造厂,生成的实例对象在运行前必须初始化,这个工作如果在定义数据成员时都加上初始化属性值,生成的对象雷同,就没有必要定义类了。所以每个类都定义一个构造实例的方法叫构造子(constructor.C++译为构造函数,因为它的操作只有函数),用户设定不同的参数,就可以构造出不同的实例,在2.4.2节例的main()中stkl是可装100个字符的堆栈,stk2可装20个字符的堆栈。构造子一般与类同名,在声明实例对象时也就等于在给类对象发消息。同名的构造子可以定义多个,这样,构造实例的方法多样编程就很方便。比如,可以定义一个不填参数的方法(无参),当用户不关心堆栈大小时,无参方法就是缺省的,由系统给定10个或其他数目。再如,若根据程序运行情况动态地生成实例时,其构造子也不同。方法的名字相同,所带参数不同,即方法体不同,这叫重载(overloading),即一个名字代表了好几个方法。编译或运行时根据参数的类型、个数、次序匹配。析构子(destructor)和构造子相反,当程序不再需要该实例对象时,及时撤销以释放它所占据的空间。析构子的定义是在类名前加‘~’号,不带参数,在main()中写以下语句:stk2.~char_stack();则撤销stk2对象。接口类有什么用呢?用处很大! 因为同一接口类可以由不同的类实现,而应用程序关心的是接口提供的行为而不关心实现的细节。例如字符堆栈,一个类用数组作栈体,一个类用链表,都可以实现堆栈操作,具有极大的灵活性。更有甚者.在网络互操作之中,一个站点是32位字长的机器,一个是64位的,都有类来构成这个接口类。通过远程调用(RPC或RMI)都可以实现应用程序要求的接口型构提供的功能。接口类还有利于解决多继承带来的混乱,见下小节。类定义中还有一个重要的内容是定义谁是它的父类,见下小节。2.4.4类继承类的封装保证了程序的模块独立性,这样,调试程序比较方便。但封装也带来问题,相同的数据、相同的操作,每个类封装一套(例如四则运算、两数比较、Hash索引等)那就太繁杂了,继承能解决这个问题。每个类都可以派生许多子类,子类继承父类的属性和方法。子类又可以派生它的子类……老祖宗的属性和方法可以一代一代传到最新派生的(子)类.把最“老”的类叫object,把一般四则运算、两数比较等大家都用得到的属性和方法定义在其中,以后派生类就不用写了,只定义派生类“自己的”属性和方法.构成树状的继承体系,如图2.20所示:类C122中的属性集是:Atrl22,{AO,Al.A12,A122}方法集是:Mtd122=弓MO.Ml,M12,M122}把这个类继承体系做成类库,显然,每次作程序设计时首先浏览类库,查看有没有和要设计的类相近的类:如果全是新设计,没有相近的类,那么就写出本类的全部定义。此时隐含它是挂在Object类之下,它继承了Object中一般四则运算,数、串比较等系统定义的方法和属性如果要设计的类与类库中某个类的定义相近,例如C122,就从它派生:ClassCI22I:[public]C22{Atrl22l//只定义自己增加的网性集//隐含继承了(AO.AI.A12,A122)属性集public:Mtd122l//只定义自己的方法集//隐含继承T长Mo.Ml.M12,Mi22)方法集}显然.在定义自己的属性和方法时名字和原有类库中类的属性和方法名字重复了,就产生了覆盖(override),例如C1中有print(),C1221中也有print(),那么,本类的实例就不继承被覆盖的属性和方法了,只按自己定义的属性和方法执行.如果浏览类库查出了某个类正好是自己要设计的类,例如Cn22,那么只要按它的构造函数在使用它的实例的地方声明实例(填上参数)就可以了。随着使用日久.类库中派生出各种各样的类,如果使用良好就将它定制(Customized,即更加完善,为较规范的类)入类库,在庞大的类库支持下,绝大部分类不用设计只按上述第三种方法设计实例对象,程序设计就完成了。而且使用越久类库越有价值(因为所有可能出现的bug都己经改正过了).以下给出了一个类继承的实例:假设某公司的雇员分为两类:计时雇员和月薪雇员,而他们的工资计算方式是不同的。比如月薪员工有医疗保险而计时员工却没有。月薪员工又分为两类:专业人员和项目经理。类似地,他们的工资计算方式也是不同的。比如项目经理可以有项目提成而专业人员则没有。显然,首先要定义一个雇员类,它记录了所有雇员都拥有的属性和方法。然后再定义一个计时雇员类,它继承了雇员类的属性和方法,并有自己专有的属性和方法,同样,月薪员工继承了雇员类的属性和方法并有自己的专有特性。当然,根据需要,可以再定义两个类:专业人员类和项目经理类,它们都是月薪员工类的子类。经过分析,可得到图2.21所示的类继承图.2.4.5多态性注意到上述类中有许多方法是同名的,但是,由于它们所属类不同,编译器不会弄混。例如Print-List方法: 这时p一>Print_List()就是多态的,有时是父类Employee实例的数据,有时是子类Manager实例数据,因为面向对象允许子类实例就是父类实例。*p所代表的既可以是雇员也可以是经理,这种多态性的好处是不因雇员经理数量多少而更改程序.请注意在给定的程序C++中若没有虚函数Virtual关键字,这种自动动态切换是无法实现的。因为C++是编译型,编译时若无Virtual告诉它,它只认为*p是Employee类中的一个无名实例。这种动态联接它要执行的是Print_List的程序体,学术上称作动态束定(DynamicBinding)。由于继承,属性和方法可能重名,这就产生了多态(同一名字执行内容不同)。静态(在编译或连接)时就可以分辨的叫重载(overloading),例如多个构造函数是最常见的。运行中根据执行情况才能决定束定(也叫绑定)到哪个方法体。多继承2.4.4节中雇员类体系结构的派生类只有一个父类,体系结构是树模型。但客观世界多数事物是多继承的。例如一个北航的大学生,他是北京市民,又是航空专业,又是大学生。只是讨论不同问题时强调他的不同侧面,继承不同的属性,如市民应具有的属性:性别、年龄、婚姻、职业、户口所在地、身份证号码……大学生应具有的属性:学号、专业、年级、入学成绩、各学期选课、成绩、奖学金……查看他是否选民用前者,选拔优秀生用后者.办理出国留学就要用到两者。多继承的体系结构是一个网状模型.C++就支持多继承,在定义类时写:class类名:[public]父类名1,[public]父类名2,…{本类属性集public本类方法集}单看一个类这样很清楚,问题是如果按树模型严格的辈分关系,很有可能把该类父类的重孙子类当了该类的第二个父类继承关系形成回路,类库的查找、派生变得十分难以管理。Java和C#坚决不用多继承,类库是单继承的树模型,而实际问题要多继承怎么办?用接口类解决,做如下定义:class类名:[public]接口类名1,[public]接口类名2…{//下略由于接口类不入类库,它随应用程序,应用程序运行完了它也就消失。从前面可知道接口最后还是要接到类库上(它自己没有体无法运行),所以,也一样能提供C++的多继承的功能。把继承之间的复杂关系交由程序员负责,不影响其他人使用类库。在这里再次看到了接口类的作用。可见性规则有了继承关系以前设定的可见性规则就复杂化了,既然是‘血统’关系以前不对外开放的(私有)成员,对子孙就要开放。类定义中protected指出该成员对家族外是私有,对内公有。真正私有的还只限于本类方法访问。图2.23 给出派生类中成员的可见性.现在又有了问题,父类以前定义的成员可见性(公有、私有),子类继承之后是否仍然维持原状?嵌套类可见性当一个类的成员不是属性和方法而是类class时,叫嵌套类。这些子类和其父类不是派生——继承关系而是直接包容关系。如同一个工厂有车间类、科室类、门市类、后勤类,它们聚集在一起共同构成工厂类的一部分。子类彼此之间的关系并不密切,车间的业务和门市的业务相差很大,所以只能聚集。至于锻铸(热)加工、机械加工类车间业务虽有差异,但是“车间要加工零件”是一样的,有许多共性,可以从车间类派生。而机加一车间、机加二车间就是机加车间类的实例了。嵌套类成员的访问性控制和普通成员的访问性控制的关键字解释是一样的。没有修饰符的嵌套类为包容类私有类,它的实例外部不可访问,加public的修饰符后的内嵌套和一般普通类的使用没有什么差别,只是声明实例时:Outer.SomeclassP(100);写上外包类的名字。}其中与类和继承有关的关键字public,abstruct,protected,private,static,extends本章均已解释。若要final修饰类,该类不再派生;修饰变量,该变量等同于常量。类体系的组织由于继承和面向对象化,有了类库编程越来越简单。这些支持编程的类(VB叫构件和控件),有的是应用程序的一部分,有的是调试工具和翻译工具(Debugger,ComplierLinker等),有的是工作平台的支持(从最直接的应用到操作系统)。如图2.24右图所示,系统类、系统工具类、应用基础类、应用类不分彼此,都是类对象,直接或间接都是Object的子类。如图2.24左图所示,由于类过于庞杂,面向对西提出名字空间(NameSpace)的概念作类体系结构管理。名字空间可以嵌套,每个名字空间下有若干个类。名字空间实际是子类系统合子系统的别名。因为在分布式环境下如此庞大的类支持不一定在一个站点,也不知道某串子类支持在哪里。有了名字空间系统就可以按名自动索引使用。最新面向对象语言Java、C#均设NameSpace机制,Java叫Package(包),用Import语句引入:Importjava.awt*;//指awt名字下所有的类Importjava.util.Date;C#中用using引入:UsingMiscrosoft.CShrpintroduction;UsingSystem;2.5网络计算机时代的编程语言面向对象语言和技术是分布式客户/服务器计算时代的产物。对象计算模型天生就和分布式网络计算模型对应。客户站点即客户对象,服务器站点就是服务器对象,它们相互发消息,共同协作完成应用。它们可以建立在局域网、广域网上,下层有网络软件支持,上层用面向对象语言编程。调试后增加一道部署工序,把每个对象安装到各个站点上。分布式客户/服务器计算时代(1985一1995),大为改变了单主机计算时代(Mai。加mocomputing,1985年之前)应用开发观念.服务器提供的程序往往是事先编好了的.客户端只要编一些“使用”服务器提供的“服务”(程序运行后的结果)的简单对象(程序),就完成了应用开发。只有不存在这种服务时才去开发服务器端的程序。如果在开发时网上有相近的服务,把它下载下来稍作修改即可交活。再者,开发客户端和服务器端程序也不是像单主机计算时代那样,基本上是从第一句写到最后一句,而是在系统提供的控件、构件(系统类)的基础上,选定参数生成实例对象.简单应用几乎不用编程.但必须遵守系统支持提供的使用方式和小范围内可变的格式(输入、输出).随着网络范围进一步扩大,Internet把全世界计算资源联接起来,计算机应用的概念又面临一次大飞跃。世界上有无数资源,你要的应用网上几乎全有,如何找到它延关键问题。找到之后连下载也不要,只需订阅(租赁),到时候发消.息,它回复你计算结果。这如同打电话付费一样。“ 软件就是服务”,可以想象,应用程序的开发和使用有多大的变化。软件开发多限于应用服务提供商(ASP)。专业分工很细,他们可以做得十分完关。在自己选定的业务范田内,把功能件做成标准的构件.并由他们自己升级维护:使用者只要有一个连接各种服务的小程序,开发者的工作重心是问题分析、建立计算模型、建立计算框架、连接上选定的构件(填满框架)。这个框架的软件实体一股不在开发者的站点上,他只有一组网址。网络计算目前在大型应用中仅限于某些行业的电子商务.还没有成为应用主流技术,本节只作简略介绍。2.5.1HTML和XML2.5.2脚本语言Java语言的特点当今的编程语言很少建立在全新概念和全新语法上.历史上Smalltalk创造性地实现了面向对象编程,但因其语法独特,使用方式新颖,本身始终难以推广。如今它的思想、关键概念、使用方式纷纷化为已有语言,C++是成功的范例。比Smalltalk在面向对象方面更完善的Eiffel,推广也不容易。所以,Java采取“改造”C++的策略,基本是C++哪里不合适就改哪里.平台无关最大的变化是建立Java虚拟机(JVM).所谓虚拟机就是不管你操作系统的平台是什么,建立一个解释Java代码的执行系统(RuntimeSystem),正确地执行。本机操作系统是实在地机器执行系统.是实现虚拟机的手段。每台机器若都能实现虚拟机的功能,则Java的平台无关性就得到第一层的保证。第二层,执行的代码必须统一。Java源代码是统一的,但编程可执行代码就不一定了。如不同类型字长不统一,有的整型数32位,有的64、128位。再如复合操作码本可简化操作.但不同系统复合习惯不同,带来微小差别……所以,Java采用泛代码(Unicode)的中间码技术。泛代码也叫字节码(bytecode),操作码和最小字长都是16位的双字节。这样,Java源代码变为所有虚拟机都可执行的中间码,保证大家对Java源程序理解一样,执行结构一样。其示意图如2.26所示。编译一个解释执行Java代码是解释执行的,这是指它的蹭中间代码。从Java源代码要经过编译器Javac翻译,这不妨碍源程序在类库支持下派生类,,因为类支持也是中间码。Java虚拟机首先要装入类,检查代码(安全)后解释执行。为了提高解释效率,发展了即时编译技术(Just-in-time)。即对于多次重复或耗时较长的段落编译后,作为模块暂存,下次遇到则直接执行目标码。即时编译技术目前还在向智能化方向发展。更为纯粹的面向对象语言Java无全程变量,无主函数main(),从而函数全改方法。类中的方法均缺省为‘虚’函数,更加支持动态加载。取消C++中的模板而用动态的实例类型化Object类。为了封装更干净,取消了C++的头文件和C的预处理。一个或多个功能相关的类组成为包(package,即名字空间),从而为构件规范化提供了基础。支持网络安全比C++的强类型更强,要求显示方法声明,编译器可以发现错误消息。取消了指针,杜绝了内存的非法访问。增设自动无用单元回收集,减少内存分配带来的问题。特别是代码传输之后由程序员回收无用单元是不现实的。增加泛代码验证,以防下载非法代码。此外,下载代码和本机支持代码放在不同的名字空间。 用异常处理机制增强程序健壮性。可以指定线程安全ThreadSafe变量。支持多线程多线程即共享资源的并行执行的‘子进程’。可以提高图形用户界面的交互性能。C++中多线程是由程序员控制锁来完成的。Java的Java.lang包提供Thread类大为简化了多线程就用程序的开发。线程概念参阅第八章。接口作为类型方法型构(接口)和方法体可以显式分开在C++中己经实现,方便了编程和修改。只要接口(及其所带参数)不变,方法体可随意修改而不影响该方法的程序。然而,把接口作为类型有其更深远的意义。接口类是一组方法型构而无方法体,除有常量外没有属性。接口类和普通类一样可以嵌套和继承,且可以多继承,可以声明接口实例。接口类由普通类实现(写出各方法的体)。请看以下示例:支持网络上分布式应用Java既可以用于客户端也可以用于服务器端,只要该端有虚拟机。可以以Applet嵌入主页到处传送,也可以直接分发和部署。因为Java提供了java.net包,通过包中的类,可以完成各个层次上的连接.如URL(同一资源定位器)支持Java应用程序通过Internet以打开和访问远程对象.并且使打开远程文件和打开本地文件一样容易。包中的Socket类可以提供可靠的流式网络连接。从面向对象到基于构件编程接口类一实现类的显示分开,实际是反应了设计和实现的分离。可以按传统的方法先设计后实现,也可以先实现(把类做成较为规范的构件component,如同软件‘集成电路片’)后设计应用程序。从而引起一场程序设计或软件开发的革命——基于构件的编程。试想,各行各业软件的项尖高手大量生产各领域常用的软件IC,应用开发者只需写接口类,形成体系结构(架构),再外购(或租赁)软件IC作为软插件插在本机操作系统上就完成了设计和实现。以后选定参数发消息就可以使用了。软件开发的重点向分析设计转移。为比较面向对象分布式应用和基于构件的分布式应用,请看示意图2.27.图中构件以软插件方式插入所在站点的操作系统平台,垂直的方框即与环境的接口。图中封闭曲线为实现接口的体。接口类均无体。对于一个站点上的接口,其实现接口的体可在另一站点上的构件之内,如图中构件4,它支持a2、a8、a9、b5、c8、c9方法接口小结程序设计语言一般指高级语言,是为了人们方便编制程序而设计的.它最终还得翻译为机器代码由机器执行.它是在低级语言(机器、汇编)的基础上为了方便使用而对低级语言进行的抽象.随着编译技术的发展和程序设计语言描述能力要求的进一步提高,高级语言也在不断发展.把面向机器编程的第一代语言(IGL),即机器码、汇编语言排除在外。第二代语言(2GL)即早期的高级程序设计语言就有了数据类型,执行控创、过程和函数的概念.当然,最本质的是赋值语句和判断语句.如果一个程序不能改变一个变量的值或者不知道如何去改变变量的值,那将毫无意义!当然.赋值的强制性给程序正确性带来许多问题.60一70 年代发展了不用赋值的函数式语言,但由于使用不便而没有得到广泛应用.显然,如果没有赋值和判断,不讲究给什么样的数据赋值,如何组织利断结构,只按算法过程凑程序,这样编制的程序稍大一点就难于阅读和难于修改.第三代语言(3GL)虽然也是面向葬法过租的,但程序结构和数据类型有讲究.这就是结构化程序设计语言.程序结构只能走三种结构(即顺序结构、分支结构和循环结构)的嵌套,无论是主程序还是过程均一样.数据加强了类型(以便自动检查帮助少出错)和用户定义类型.程序的结构化和模块化导致了人们对封装概念的新认识.这样数据就分成局部量、全程量、静态量、持久量,到3GL后期引出了抽象数据类型、程序包、分别编译、定义(规格说明)和实现(程序体)分离等概念和机制.影响到程序设计从设计数据结构、构造算法.到设计描述程序体系结构再编码实现各模块.这种思想的延伸就出现了第四代语言(4GL).4GL语言的程序只描述程序应“做什么”,而不必编写“怎么做”的实现模块(由4GL的解释器自动完成各模块的实现,在后面章节要讲到的数据库查询语言SQL,就是4GL语言的代表).在特定的应用领域,如界面语言、数据库语言,4GL得到巨大的发展.然而实现模块仍然是开发者用传统语言开发的,它必然限定于某些特定域的应用.即使在特定域中4GL往往表达能力不足,过于复杂的应用还要辅以传统(3GL)语言开发。用户封装类型和相关的操作(函数或过程)构成更高抽象的数据类型(ADT)。在抽象数据类型的基础上80年代以后发展了面向对象编程语言.类就是抽象数据类型,但比它有更丰富的语义,增加了继承,多态的机制,使得程序对象很容易模拟客观世界对象,并支持软件技术要求的局部化、重用性、可扩充性,分布性等诸多性能.因此,90年代是老语言向对象式扩充的年代,如C扩充为C++、Ada95扩为Ada95、Pascal扩展为objectPascal、COBOL扩充为ObjectCOBOL,还出现了一批断的面向对象语言,如Eiffel、Java等。20世纪90年代网络计算普及迫切需求能在站点之间进行传递信息的语言.HTML因其简单易于实现浏览器浏览很快成为Web页面的数据描述语言,以后发展为用户可以自定义标签的XML.这样,从面向文档的简单数据描述转为对结构数据本身,其文档格式另有XSL转换(成HTML).由于HTML和XML都不是编程语言,只能回答数据在什么地方、什么类型,数据之间有什么结构关系,而不能以算法加工改变数据,所以本章只做极为简略的介绍,但它们嵌套的脚本语言Java(App1et)是编程语言。脚本语言其有类型、变量、数组、表达式、关键字、控制结构、赋值语句、函数调用等小型语言的一切特征.既可作为过程语言编写计算函数和算法过程,也可以在对象模型背景下编写对象属性和方法,定义实例变量.但脚本语言往往受限于所在背景,功能有限。Java是网络计算时代的主导语言,它是面向对象的、分布式的,解释的、健壮的、安全的、平台无关的、可移植的.动态的、高性能的、多线程的现代编程语言.特别要注意接口和体的分离.接口作为类型为今后网络软件带来了深远影响:从面向对象转到基于构件.'