还剩16页未读,继续阅读
本资源只提供10页预览,全部文档请下载后查看!喜欢就下载吧,查找使用更方便
文本内容:
第章程序控制7冯•诺依曼机器模型变量的时空特性对程序中求值的次序是十分敏感的程序员用这类语言要得到预想的计算,就要善于驾驭求值次序表达式的求值次序是最低层的程序控制,在前面章节中我们已经介绍过了在它的上层是四类控制顺序控制、选择控制、重复(迭代)、函数或过程调用本章要讨论它们再上二层是对程序模块的控制包括一个程序的各模块组织以及它们与环境(软、硬件平台)的相互关系本章部分讨论它们并发控制也是一类控制,它可以在语句级,特征块和模块级实施并发控制涉及并发结构模型和通信机制后文第13章还要讨论本章只讨论顺序程序控制结构化程序最重要的成果是程序控制它使程序成为可分析的、分层结构的,程序正文和程序的执行逻辑比较一致同时也使编译器易于实现,尽管它已定论,在前述章节中也零散讨论某些部分本章对此作一系统的小结一般概述
7.1程序控制是控制机器执行计算的次序我们把表达式求值规则看作是微观的则最基本的控制对象是“语句(Statement)所表征的计算在英语中statement一词有‘陈述’的意思,易于和声明混淆,所以,有的书上主张叫“命令”(command)对于命令式(Imperative)语言叫命令也许更好,本书这两个词通用早期的高级语言脱胎于汇编语言,语句和汇编语言的一条指令对应它相当于程序世界的一条“指令”是可以独立处理的最小单元(当编译或解释时)程序员通过语言代码陈述的先后表达是程序最基本的顺序控制但是一个复杂的计算单有顺序指令是编不出来的汇编语言的JUMP指令在程序世界就成了GOTO命令它打破单一的顺序,使程序有声有色可以毫不夸张地说命令式语言只要有赋值语句V=EXP,简单的逻辑条件IF(e)和GOTO语句就可以编出一切计算程序(输入/出除外)按IF条件跳过一段代码是很容易的,GOTO返回到某起始语句即可实现重复早期的语言都脱离不了GOTOGOTO和语句标号把顺序的程序代码切分得七零八落编译时只能把它们分成很多可连续执行的程序小块(显然,GOTO语句的前后语句不能在一块)的语句组这就是模块(Module)一词的来历对于重复执行的块单给出关键字和截止处语句标号,并引出循环域和嵌套循环的概念这是模块封闭性的开始GOTO只能从循环域内转向域外,反之不行但当时重点还在想出方便的GOTO表示上单GOTO语句FORTRAN就有五种之多流程图的发明使得编程思路清晰,而程序代码仅仅是实现流程图的表示工具,它本身的可读性未引起足够重视随着程序尺寸的增长,脱离了流程图,程序正文越来越难读(见第2章图2-3示例)即使不太大的程序,几十个GOTO来回穿梭就成了戏称的“乱面条”程序难读、难调、难修改但当时人们醉心于精巧的设计终于酿成60年代初的软件危机自从1965年E.Dijkstra提出“GOTO语句是有害的”以来,60年代中叶在西方软件界引起一场争论,因为它动摇了赖以构成巧妙计算转移的根基既然它有害,那么,首先要回答,不用GOTO行不行?1966年Boehm和Jacopini回答了这个问题任何流程图的计算逻辑都可以用顺这样一开始条件不满足就不要进入循环迭代元素41963年美国Galler和Fischer对FOR循环提出迭代元素Iteration element概念,以后C语言的发明人D.Ritchie把它用于C迭代元素使FOR语句的语义通用化,即循环表达式和循环语句能等价地实现,它们的一般形式是iterate〈控制元素》〈迭代域》=iterate a;b;c〈迭代域》执行过程是
[1].进入迭代域之前先执行一任意表达式表〈心,目的为了初始化,执行后控制转到表达式〈b
[2].b是任意表达式但其结果值只以逻辑值解释为‘假’,非0为‘真若为真执行迭代域后执行表达式c若为假则跳过迭代域出口
[3].c是任意表达式表,执行完后控制转移到b控制元素后接迭代域如果是语句,则此域中是任意简单或复合语句,执行这些语句都可能有副作用,如输入/出语句、赋值语句如果是迭代表达式,每次执行迭代都是在计算程序对象这些程序对象往往是聚集数据结构,它们就是整个迭代表达式的返回值迭代元素和一般FOR循环的最大差别是对迭代元素不加任何限制语句,表达式,连续赋值,空都可以所以有例7-2同一问题的六种表达迭代域中做的事也可以在迭代元素中做,效率更高但牺牲了可读性从另一个意义上讲,为程序员提供了互易的灵活性正是由于迭代元素可以是任何表达式和语句一般说来,它不能像FORTRAN那样把控制变量放在索引寄存器中,得到高效利用简单寄存器变量精心设计的个别问题也能达到隐式迭代控制
7.
4.2所谓隐式迭代是语言系统自动实施迭代这对第四代语言,声明式语言函数式逻辑式都非常重要,而且是语言发展方向上的重要机制隐式迭代一为对聚集数据每个元素自动逐个加工,一为实现某种特定算法,现分述如下1对聚集对象的迭代APL语言最突出的特征是隐式迭代,因为它是专门处理向量语言的有数据元素和向量的混合计算,有向量和矩阵的混合计算,如果象常规语言每次计算只能在基本数据类型元素上进行,程序非常臃肿,所以一个运算符自动迭代计算变量所代表的对象上的每个元素,变量根据值定义情况既可以代表简单对象,又可以代表向量、矩阵如前所述,这种语言是无类型动态存储分配的例7-5APL的隐式循环▽ANSWER-A FUNCB〃声明定义两参数函数名为FUNC
[1]ANSWER-3XA-B〃函数定义▽X-491625//X,Y束定为整数向量Y-1234Z-34P123414916182764〃Z束定为3X4矩阵若有以下表达式,见右下栏的解释运算符=为比较表达式解释X的每一元素加1=5101726X+1X+Y X,Y对应元素相加二5111929X FUNCY3*X-Y=H254571|X+1=X+Y5101726,5111929=1000|只有个兀戏样Z=1将1与Z的每个元素比较,相同为1,否则0,得100010001000如果W是纯量,结果是一向量其元素是W+/z=w在Z中每行出现的次数,如W-4时为110如果W是和Z一样的矩阵,其结果是一长|度等于Z的行数的向量,元素为每行中W,Z元素匹配的位序|类似的LISP的map函数的例子见例6-12它处理的数据类似APL的向量,主要是表数据库查询语言如dBASE加工的文件和记录也是聚集数据对象在处理时也要用隐式迭代,见下例例7-6一种类似dBASE的dBMAN查询的隐循环设每个雇员一个文件,当前记录类型域是lastname sexhours grosspaywith held有以下dBMAN命令
[1].display allfor lastname=Jones andsex=M
[2].copy tolowpay.dat allfor grosspay10000
[3].sum grosspay,withheld togrosstot,wheldtot allfor hours0第一行找出所有男性名为Jones的雇员第二行是创建一新文件lowpay.dat把当前文件中税前工资小于10000的记录复制上去第三行求税前工资总合并扣除那些小时工资已登记了的雇员的那些部分至于它内部是如何迭代的算法程序员不用过问,只知道它在多个顺序组织的文件上进行的2回溯有一些计算是在已有数据集上寻找匹配实现的,程序员只需写出匹配表达式,执行这些表达式时是在数据集上一一搜索,如果沿某个搜索路径无一匹配成功,则自动回溯到上一数据结点,直到穷举所有数据库集Prolog,SNOBOL,ICON就是这类语言我们把回溯Backtracting也归到隐式迭代一类先看Prolog的一个例子例7-7Prolog查询的回溯实现如果数据库中已列出以下事实fl.likes Sara,John f
9.does Mike,skatingf
2.likes Jana,Mike flO.does Jane,swimmingf
3.likes Mary,Dave fll.does Sara,skatingf
4.likes Beth,Sean fl
2.does Dave,skatingf
5.likes Mike,Jana fl
3.does John,skatingf
6.likwa Dave,Mary fl
4.does Sean,swimmingf
7.likes John,Jana fl
5.does Mary,skatingf
8.likes Scan,Mary fl
6.docs Beth,swimming说明8名青年相互爱慕和喜好的运动溜冰或游泳今有一查询找出相互爱慕且有共同运动嗜好的一对,并指出是什么运动Prolog查询目标为-likes X,Y,likes Y,X,does X,Z,does Y,Z执行过程是逐一匹配这四个谓词,我们暂叫gl,g2,g3,g4四个子目标,都能匹配即为解具体步骤是匹配结果
[1].沿数据库匹配第一子目标fl gl X-Sara,Y-John
[2].匹配g2,沿fl-f
7.Y对X不对失败
[3].回溯重配gl,f2-gl X-Jana,Y-Mike
[4],匹配g2,沿fl-f5f5-g2g2成功
[5].匹配g3,沿f9-fl0fl0-g3X-Jana,Z-swimming
[6].匹配g4,f9,Y对,X不对,失败
[7].回溯第
[5]步fll-fl6无新匹配回溯第
[4]失败失败步f6-f8无新匹配回溯第
[3]步重配gl f3-glX-Mary,Y-Dave
[8].重匹配g2,f6-g2g2成功
[9],匹配g3fl5-g3X-Mary,Z-skating
[10].匹配g4,按Z找Yfl2〈-g4Y-Mar y,Z-skat ing[
111.查询全部谓词满足f3〈-gl f6〈-g2fl5-g3fl2〈-〉g4X=Mary,Y=Dave,Z=skating
[12].从f4〈-gl再查找有无另一组解重复第步,结果无新解本例是在一顺序的数据结构子目标结点,gl,g2,g3,g4上做回溯,基本思想是从某一结点出发一试探新子目标一无解回溯到原目标另取新值直至穷举所有值一再试探完成所有子目标回溯的数据结构更一般的是多叉树,回溯的每一步都有许多逐个匹配这就要靠隐式迭代完成显然,回溯算法效率是不高的异常处理7-5程序运行中常常有意想不到的程序错误,例如,数组越界,栈溢出,被零除等,都会引起程序无法执行下去,也就是出现了异常exception情况在早期语言的程序中,出现了这种情况就中断程序的执行,交由操作系统的运行程序处理操作系统先调用诊断程序,如果诊断程序能处理,处理后继续执行否则打印尽可能多的信息后,停止这个程序军用语言的嵌入式应用不能容许程序有了错误交由监控器处理,或停止工作这样,正在执行飞行或战斗任务的军器会因失控而毁灭因此,即使有了程序错误,程序员也应事先要想好应急措施使之降级运行或实施某种动作以求安全这些非常规程序叫异常处理段,待异常引发后执行因此,Ada引入异常处理机制90年代非军用语言C++也有了异常异常中断程序执行转而执行异常处理段,在这个消极意义上,它也是程序控制异常定义与异常处理段
7.
5.1异常是引起程序不能正常执行的事件为了处理异常必须先给异常事件命名Ada语言就预定义了五种异常名•CONSTRAINT ERROR凡取值超出指定范围叫约束异常•NUMBER_ERROR数值运算结果值实现已不能表达叫数值异常•STORAGE ERROR动态存储分配不够时,叫存储异常•TASKING_ERROR任务通信期间发生的异常叫任务异常•PROGRAM_ERROR除上四种而外程序中发生的一切异常,都叫程序异常.如子程序未确立就调用等用户可在任何程序的声明部分定义异常名〈异常名〉:exception;即为用户定义的异常,预定义的异常可以显式也可以隐式引发,而用户定义的异常必须显式引发用以下引发语句raise[<异常名>];引发语句可出现在任何可执行语句所在的地方一旦执行到本句,则本程序块挂起转而执行本块异常处理段中同名的异常处理程序每个程序块都可以在该块末尾处设立异常处理段declare〈正常程序声明〉;[〈异常声明>];begin〈正常程序代码〉;[〈引发语句>];exceptionwhen〈异常名〉二X处理代码》一异常处理段when[〈引发语句>]—一般再次引发原异常(缺省名字)end;Java/C++的异常处理和传统的异常机制基本一样,只是它是面向对象语言一切都是对象,异常也是对象系统提供一个throwab/e类,由它派生多个子类,定义各种异常不仅如此用户定义的异常也由它的子类派生这样,随着长时间的积累各种程序中见到的异常都可以处理Java/C++的异常引发也是两种显式引发通过throw/throws(抛出)语句;自动引发又运行时系统判断是哪种预定义的异常Java/C++认为程序员定义异常总有自己的估计给捕捉异常划定一范围可以减少捕捉开销当然,范围之外的异常就靠自动引发了异常引发与异常传播
7.
5.2用户定义的异常必须显式引发,而系统定义的异常一般自动引发因为数组越界,被零除程序员是无法指定出错地点的但在处理段中可再次显式引发,其目的是让外嵌的程序块的处理段来处理如果没有exception段,或有了exception段找不到匹配的处理程序它自然要往上找,看各外嵌程序块中有没有与此同名的异常处理程序如果没有,则在调用语句所在的块中引发这个异常(隐式)如果还找不到,就到更高层调用的程序块中找如果还找不到则主程序停止执行交由操作系统处理这就和没有异常设施的程序一样了我们把这种逐级引发找处理段称为异常传播(propagating exception)o例7-8Ada的嵌套异常及异常传播with TEXT_IO,MATH_FUNCTIONS;use TEXT_IO,MATH_FUNCTIONS;procedure CIRCLESisDIAMETER:Float;MAXIMUM:constant Float:=
1.0e6;
[0]T00_BIG_ERR0R:exception;PUT pleaseenter anfloat diameter.;begin
[1]loopbeginGET DIAMETER;if DIAMETERMAXIMUM then
[2]raise TOO_BIG_ERROR;else
[3]exit whenDIAMETER=0;end if;
[8]ONE_CIRCLES DIAMETER;PUTz,Please enter another diameter0to quit:;
[4]exceptionwhen TOO_BIG_ERROR=PUT Diametertoo large!please reenter.〃;
[5]end;
[6]end loop;PUT^processing finished.,z;PUT New_line;end CIRCLES;procedure ONE_CIRCLE DIAM:Float isRADIUS,CIRCUMFERENCE,AREA:Float;beginCIRCUMFERENCE:-DIAM*
22.0/
7.0;RADIUS:=DIAM/
2.0;AREA:二RADIUS*RADIUS*
22.0/
7.0;PRINT_CIRCLE DIAM,RADIUS,CIRCUMFERENCE,AREA;
[7]exceptionwhen NUMBER_ERROR=raise;end ONE_CIRCLE;这是一个非常简单的程序,每读入一个直径计算并打印半径、圆周长和面积是一个交互式程序,直到输入直径=0停止CIRCLES过程中在
[0]处声明异常TOO BIG.ERRORo每当输入直径值大于最大值MAXIMUM时显式引发异常,在
[2]处引发后程序控制跳到
[4]进入异常处理段找名为TOO_BIG_ERROR的处理程序本程序打印一行提示后经
[5]、
[6]又回到
[1],循环再做如果直径二0,它就跳到
[6]出循环,打印“处理完了”信息后结束本程序如果不大不小,则调用ONE_CIRCLES过程,这个过程中没有定义用户的异常但显式指明系统定义的异常NUMBER_ERROR的处理即由于不可知的原因计算兀值220/
7.0或其它数值时发生了问题,它就自动弓|发NUMBER_ERROR,引发后首先在自己的程序块中找处理段,进入
[7]终于找到,但其处理是再次引发NUMBER_ERROR,本段没有其它处理了,它就把这个引发通过CIRCLES中调用语句传出,则在
[8]处引发该异常于是它又在CIRCLES中找处理段,进入
[4]后没有这个处理程序,这才迫令本程序停止运行,把NUMBER_ERROR异常交给操作系统的运行管理程序处理这就和其它传统语言处理一样了异常引发和处理后不像子程序调用返回原处,而是从处理它的处理段出口,执行以下程序只有交由操作系统处理,程序才结束悬挂,终止执行否则主进程一直等着悬挂异常进程的处理Java的异常我们在第15章中还要介绍止匕外,只演示其抛出过程及传播路径例7-9Java的异常定义及异常传播
[1]class UserExceptionextends lOException{〃定义用户异常类private inti;
[3]UserExceptionint j{〃构造一个用户异常对象
[6]public StringOnlyTest{〃测试异常对象构成否return UserException+i;
[9]}
[10]public classExample{
[11]static voiddmeint jthrowsUserException{
[12]system.out.point/n dme+j;ifj0
[14]throw newUserExceptionj;
[15]system,out.point/ngood;
[17]public staticvoid mainstringargs[1{
[18]try{dme4;dme15;}
[21]catchUserException e{system,out.point/n Exception is+e}
[25]}从
[1]至M9]行是用户定义的异常类一个方法
[3]是构造本类实例,一个方法只是测试该实例用上没有,返回调用号第
[10]到
[25]行是一个示例Example类,它有两个方法,一为dme,一为main.main是Java的规定,不能像C++那样作为单独的主调函数只是一主调方法,由它向dme发消息dme4,dem
15.第
[14]行的throw是因为j10时要扔出一个新的UserException,有了new则按第
[3]行的构造方法构造新扔出的异常第
[11]的throws xxxx必须和方法联用,指明该方法只检测扔出xxxx类的异常,当然常规检测除外.第
[17]行的主调方法内容很简单,在此y块中故意放dne15它大于
10.当调用发生时它经过
[11]到
[13]行扔出UserExceptionl5”那麽它就在
[21]行中作参数匹配.结果相配就执行打印语句得出ExceptionisUserExceptionl5”如果在
[21]行中找不到匹配,本例交操作系统处理.这两个例子说明用户定义异常一般引发和传播的方式.表明异常引发和处理的控制此函数/过程调用要复杂得多.但它归于顺序控制器.小结
7.6•表达式求值次序是最低层的程序控制它的上层是语句组的四类控制顺序、选择、迭代、子程序调用再上一层是块间通信和约束•只要有赋值、简单的if和goto三种语句就可以编写一切命令式计算过程•goto语句是有害的,但它有利于提高效率当今程序中依然保留它或它的变体叫顺序控制器(符)O二结构化程序是只有一个进口,一个出口的三种基本结构(顺序、选择、迭代)的复合•语句括号消除了选择结构的二义性if_endif比begin_end更好•选择控制可以用命令表达式和条件表达式实现,函数式语言更仰赖条件表达式•FORTRAN在GOTO上想出许多表示法,近代语言在迭代上想出的表示法更多为了方便(灵活性)保持更多的冗余性•迭代控制有显式的和隐式的,声明式语言、第4代语言更多仰赖隐式的隐式迭代程序清晰简炼,但程序员无法干预•通用循环是while_do和do_until的复合,无限循环加上不同的约束子句可以得到不同的循环控制•计数循环是最老的控制结构之一,循环控制变量、三个表达式各语言都有差异控制范围在程序体中可否改变也有差异•C语言的计数循环用迭代元素概念它对迭代元素不加任何限制,所以C语言计数循环变通写法最多,最有用,但可读性差•隐式迭代是系统自动在同构的数据结构上完成迭代回溯是隐式迭代•异常处理是一种复杂的程序控制是将系统程序控制程序的办法让程序员使用用户定义异常、指定引发、写出异常处理段引发的规则是先查本段再查调用段,直到交给操作系统为止习题
7.1为什么函数式程序对于顺序控制显得不重要它的选择、迭代控制靠什么实现?
7.2为什么结构化程序结构比早期非结构程序易于排错?详细列出它们的优缺点
7.3为什么直到近代语言,子程序的返回语句可以多个这不违反了一进一出原则吗?
7.4试述case语句和嵌套if同异那个效率高?
7.5无限循环是一进一出的结构吗?它一般用在什么地方?只有它才能出现死循环吗?
7.6有以下类pascal程序片断k:=3FOR i:=l toK doBEGINWritein(i,k);k:=k+1END;Writein(i);这个程序终止吗?为什么?最后一句写出什么?这个程序终止,因为循环中递归变量值改变,并最终达到终值,而pascal程序中又不能改变循环终值最后一句写出
47.7何谓迭代元素?它和常规计数循环有什么不同?
7.8设计一个for循环要考虑哪些问题?试写一设计需求规格说明考虑的问题
①循环控制变量初值、终值、增量表达式、条件表达式、循环体;
②循环控制变量类型整型、离散、实型;循环控制变量全局静态、局部静态、局部变量;
③终值和增量表达式在循环中允许改变否?
④条件表达式EXP2在顶部求值还是在底部求值
7.9写出一个Prolog程序,它在一颗多叉树上作回溯匹配
7.10为什么异常处理后不能返回到异常引发的地方如果一定耍那样有什么问题
7.11试编写一C程序,利用系统调用实施绝大部分异常处理功能,该程序要求
[1]打开输入文件infile,若打开失败打印必要信息跳出程序
[2]打开输出文件outfile,若打开失败打印必要信息,跳出程序
[3]若打开无问题,扫描数据不符,打印必要信息,关闭文件后跳出
[4]若输出文件已为EOF,写不进去,打印必耍信息,关闭文件跳出
7.12C语言中有一顺序控制器continue它的作用是什么?写出continue出现在for循环时等价语义的wh ile_d o形式for Expl,Exp2,Exp3continue作用结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判定等价语义的wh i1e_do形式Expl;whileExp2doExp3;1;if!Exp42;}
7.13FORTRAN也有一顺序控制器CONTINUE,它的作用是什么?
7.14用户定义异常时,名字复盖NUMBER_ERROR并写出了自己的异常处理段when NUMBER_ERROR=…可以不可以执行为什么?
7.15有以下Ada程序片断type MONTHis JAN,FEB,MAR,...,NOV,DEC;RAINFALL:array MONTHof Float;procedure GET_WEATHER_DATA isbeginfor AMONTH in MONTH loopbeginGETRAINFALLAMONTII;exceptionwhen DATAERROR=〉PUT Invaliddata for〃;PUT AMONTH;SKIP DATAITEM;RAINFALL AMONTH:=
0.0;end;end loop;end GET_WEATHER_DATA;该程序读取每月雨量数据以判断天气若雨量RAINFALL数据格式不对,引发异常,处理时RAINFALL数组中该项为零,并读下一项异常DATA_ERROR是系统定义的异常还是用户定义的?它在哪里引发?不用异常能否处理若能改写之答异常DATA_ERROR是系统定义的它在语句GET REINFALLAMONTH处引发不用异常可以处理,改写如下beginforAMONTHinMONTHloopbeginGETRAINFALL AMONTH;ifRAINFALL AMONTH/=floatthenPUT Invaliddata for;PUT AMONTH9;SKIP_DATA_ITEM;RAINFALUAMONTH-
0.0;endif;end loop;end GET_WEATI1ER_DATA;序组、条件选择组、迭代组三种程序结构实现这三种结构严格一个进口一个出口也正因为如此可以随意嵌套将某一对进出口置换成另一结构,构成极为复杂的程序用这三种构件块构造程序可完全不用GOTO语句这就是结构化程序设计的基本思想程序控制在块一级,块相对封闭,即不许有控制从块的一部分处转移到另一块内这样程序控制就成了组织顺序、选择、迭代的结构了顺序条件选择迭代图7T三种最基本的程序结构理论探索完成后要回答的第二个问题是用结构化程序代替非结构化程序会有哪些问题?是否一切都好?事实上,结构化程序结构确有不方便之处,至少是低效例如,在一计数循环中查找一个数,头几次就找到了,余下空循环不做到底是不能出来的保留GOTO一跳出来不是更好吗?此外,若想利用某块中的部分代码不能从需用处进入非得从头上进入,这势必要保证不用部分空执行没有GOTO,就要增加很多不必要的代码这就是是否取消GOTO值得争论的原因最后大家接受了Knuth1974对GOTO使用的折衷意见GOTO是有害的,但有时对改善效率带来好处可以保留GOTO,但要加以限制使用只能向前转移,不能向后GOTO的迹线不能交叉,只能从块内转到块外,不得相反问题基本解决,争论的双方依然坚持各自信念,所以70年代以后发展起来的语言有保留GOTO的,如Pascal,Ada,C,但不提倡使用有完全取消GOTO的,如Modula,Adison等等有的保留GOTO的积极作用限制GOTO的副效应,把它们改头换面变为比较安全的顺序控制器sequencer,详见后文结构化程序除了顺序、条件选择、迭代三种最基本控制而外,子程序调用也是一种重要的控制虽然在子程序体中依然是三种基本控制结构的复合,子程序调用本身是显式地控制抽象多次子程序调用和返回,构成程序的深层嵌套对于非命令式语言,如纯函数式语言,顺序控制并不十分重要它控制计算更多的依靠嵌套顺序控制
7.2顺序控制在程序结构上是一组可顺序执行的简单语句,如果每一个顺序成分可用一顺序结构置换,顺序结构可形式地表示为SI;S2进一步扩展可为SI;S2;...Sn其中Si为简单语句(命令)一般过程语言有哪些简单语句呢?我们用最丰富的Ada简单语句作解释赋值—语句过程调用.语句goto_语句入口调用一语句出口语句返回—语句引发—语句夭折—语句延迟—语句代码—语句简单—语句二空—语句其中空_语句,赋值—语句,代码—语句不影响控制和转移,代码语句是某些信息要用机器(或汇编)码延迟语句为控制同步,指定在原地停留的时间,但无控制转移连续的赋值是最基本的运算got语句已如前述,可以往前、往后跳过任意行代码当然它和语句标号要配合标号的表示法各语言不一,有用数字,有用字符串的A(1@用<〈字符串>>exit(出口)语句,raise(引发)语句,abort(夭折)语句,都是顺序控制符exit将程序控制跳到所在块的末端,或由它指明的外嵌套块的末端C语言中与其相当的是breakraise是引发异常当程序执行到此句(一般是程序员预感要出异常设在此处)时,跳到有exception关键字处执行异常处理段(一般在本程序块末尾),且不返回异常引发规则比较复杂,详见本章
7.5节abort是强行夭折即就地停止,有的语言是HALT命令这类就地停止和暂停命令在FORTRAN早已有过FORTRAN的STOP和END分工是一为执行停止,一为代码结束由于它把程序正文表示和执行逻辑分开任设STOP为多出口与结构化宗旨有悖,在以后的版本中取消了FORTRAN的PAUSE为暂停语句,它中止程序执行,以便程序员中途作些调整,调试程序,而且必须重按回车才能恢复运行这些早期单机单用户,惜机时如金时代的便于调试的语句,以后都取消了完全可以设“断点”,追踪排错解决恢复abort和HALT是新一轮并发程序和增加了异常处理后的要求强行终止程序执行一般是信息不足,事先考虑的异常处理段不足以处理已发生的异常而进一步运行又无意义,只好终止程序的执行一般终止前应将当前信息尽可能收集、显示出来,以便调试员分析入口调用和子程序调用相似,前者是专用于并发程序的任务调用也要作参数匹配每当执行到此句,一个任务被激活,任务体开始执行它对程序控制转移到另一程序单元作用是一样的同样,return是显式指明执行到此返回至调用点因为返回是同一点,多个return一般都是允许的(违反一出口的规定吗?)早期子程序过程必有return,而现代语言则不一定(为什么)?程序总是按程序正文自上至下,自左至右地(隐含)顺序执行的表达式求值规则和顺序控制器是对这个隐含执行的“修正”,从而达到控制计算的目的条件选择控制
7.3计算机“聪明”最基本的基础是会根据条件选择它应执行的代码所有的程序设计语言都离不开条件控制结构式条件控制
7.
3.1简单的条件语句IFe,根据逻辑表达式的真假值在程序中产生分枝它本来就是想做if...then...else计算逻辑的不过早期语言用的IF e是语句级的,这种程序语言编的程序不仅不宜于阅读,且T-部分和F-部分得不到保护,甚至它有多大都没有明确界线,其它地方的goto可随意进入,程序极易出错,Algol-60正式把它看作复合语句,那么,这两部分代码叫T-子句和F-子句子句以语句结束符终止没有其它标记if语句的退化型是if_then,正规型是if_then_else,T-子句和F-子句可以是任何语句组嵌套if一开始就发现了问题,在Algol60草案论中,就发现了’悬挂else问题例如,当有if嵌套时,有if Elthen ifE2then SIelse S2它可以解释为El=true AE2=false执行S2,else属于内部if,也可以解释为El=false执行S2,else属于外部if即不管El为真假S2均可能执行解原因是可以画成多个语法树,它有二义性决这个问题是很容易的,if Elthen加上语句括号D0_ENDPL/l,BEGIN_ENDPascal就可以:then SIelsebegin ifE2if Elthen beginif S2end它的简化形式FORTRAN-77,Ada是E2then SIend elseSe2ndifif Elthen ifE2then SIendif elseS2if Elthen ifE2then SIelseS2endif endif1----------就近匹配---------1语句括号既使语义清晰又可以封闭块,所以,Pascal,C的程序员要指明块时两个语句以上,都要用语句括号begin_end或{}既然不要goto,早期的流程图也可以结构化与此对应的选择结构有nassi_Schneiderman式流程图每个语句和复合语句都是块连接,如图7-2所示块中分成F-子句分别画成『域和F-域可随意嵌套将-S块又扩充为选择结构图7-2结构选择的Nas siSchneiderman和
1.
3.2case switch在图7-2所示结构选择中是连续嵌套的选择结构对应的程序结构是:IF explTHEN IF expl THENSTISTIELSEIFexp2THENELSEST2IFexp2THENELSEIF exp...ST2ELSEIF expELSEELSESF3SF3END IFENDIF左右两表示法语义是一样的,但左边出了新关键字ELSEIF这是在FORTRAN-IV到FORTRAN-77结构化改版时增加的,增加后显得自然方便,只是在“都不”满足条件时执行最后一个ELSE不用费心记对应是那一层ELSEIF在Ada中更成为可区分的elsif,和else if是两种表示嵌套IF结构的执行,永远是执行一组代码就跳到endif o对于不同条件,if_then_elseif.・.else结构方便,清晰然而,对于同一条件表达式而多次判断其值的嵌套IF结构,这种表示显得累赘,且多次错缩进编排,程序反倒歪斜于是多数结构化语言提供case选择case Expis when vl=switch expSI;whenv2=S2;••case vl:SI;•break;when vm|vn=Sn;when casev2:S2;others=St;break default:St;两种语言的分情况语句的表示示例如下:end case;Ada的case语句C的switch语句这两种表示法略有差异,大体一致Ada的Exp不限于真值表达式(只两种情况),可以是任何离散类型表达式(C限于整数)根据取值Vi的情况跳到对应的case标号那个子句Si执行Ada是执行完子句跳到end case,而C若无break则不跳,接连执行下句.C语言设计者认为会对组合分枝带来方便,但这种过分灵活性,实际上是设计缺陷others和default选择都是在取值超出了case标号的值域时执行Pascal类似Ada,但无others选择以条件表达式实现选择控制
7.
3.3函数式语言没有语句,只有表达式,选择控制只能由条件表达式完成c语言是面向表达式语言,它有条件表达式,且编译处理最小单位也不限于语句例如,c语言语句行中可以出现++x;X++;这种无赋值号的表达式它还有?双目运算符表示的表达式,例7-1示出条件表达式和条件语句差别例7-1将b,c中小值存入a C语言a=bcb:c ifbc a=b;else a=c;条件表达式条件语句一不语言并没有必要既提供条日曾达式又提供条件语句事实上,任何条件表达式描述的计算条件语句都可以做到,只是更臃杂一些但是条件表达式返回的是一个值,它可以做函数的实参变元,在封闭的程序中继续使用而条件语句不能作为实参变元,它与程序的其它部分通信只能通过环境事实上,赋值就在改变环境的状态为此,函数式语言,例如LTSP,就用了许多括号封闭各层嵌套调用ML用文字括号如let...in等程序设计风格也是嵌套,没有或不强调顺序子句可以这么说,命令式语言大量依赖顺序命令,纯函数式语言完全依赖写嵌套函数调用可惜LISP受到早期过程语言高效的影响也包含了rplaca破坏性赋值函数和prog控制结构程序员可以使用变量并列出顺序执行的表达式这样一来就没有纯嵌套和纯顺序的语言了命令式语言主要是顺序赋值,但也有嵌套和递归函数调用,参数结合,甚至像C语言那样有条件表达式而LISP主要是嵌套,但也有赋值,和顺序执行ML,Miranda,FP力图回复纯函数式,以摆脱变量时空特性的影响迭代控制
7.4迭代一般用于处理同构的、聚集数据结构的重复操作,诸如数组、表、文件对于命令式语言迭代一般是显式的循环,例如上节讨论的结构化语言的while_do对于函数式语言迭代一般是隐式的,如LISP的map函数,它通常返回一个不断延长的表结构化语言的设计者虽然接受任何迭代都可以转为while_d表示的结构化程序理论,但为了便利于表达,迭代的表示法多种多样,赛过50年代FORTRAN的GOTO表示法,即使是同一种表示法其表达的灵活性也是空前的C语言有以下例子例7-2用for循环计算表中元素之和设表list是一简单结构,一个成分是值value,另一个成分是指向下一表元素的指针next可以有以下六种写法
[1]先赋sum初值进入正规for循环sum=0;forcurrent=list;current!=NULL;current=current-next sum+二current-value;〃循环控制变量current就用表list
[2]sum在循环内赋初值for current=list,sum=0;current!=NULL;current=current-nextsum+=current-value
[3]循环控制变量可以在for以外赋初值,增量在体内赋值current=list,sum=0;for;current!=NULL;sum+=current-value,current=current-next;〃用‘,’号连接的两语句成为一个命令表达式
[4]循环条件为空,用条件表达式完成current=list,sum=0;for;;if current二二NULL break;else sum+=current-〉value,current-next;〃不提倡此种风格[5]完全不用循环体,全部在循环条件中完成for current=List,sum=0;current=current-next!=NULL;sum+=current-value;〃效率最高.但list不能为NULL表[6]逻辑混乱,也能正确计算current=list,sum=0;for;current二current-next;sum+二current-valueif current==NULL break;〃最坏的风格,list也不能为NULL表这个例子说明语言的语法规则的灵活性,C语言的哲学是只耍不给实现带来过多开销,尽量灵活,不管程序员如何写,说得过去就行,例如,[4]中的if的T-子句是语句,F-子句是表达式,整个既是命令表达式也是条件命令这种过多的灵活性也带来排错调试的困难显式迭代控制
7.
4.1控制迭代可以在一段循环代码开始处设条件,也可以在执行一次后设条件,决定是否重复也可以规定次数,虽然都可以用最基本的while_do仿真但各有特点,语言选用也不完全一致无限循环1在顺序式命令语言中无限循环被认为是死循环,早期语言均没有,对于并发程序,为了随时接受另一进程发来的信息,它要处于活动的运行状态,就必须用无限循环以下是Ada的无限循环及其对应的结构图loop[name]:B end loop:名字name是可选的,有的语言用关键字BEGIN_REPEATF0RTH83表示,C语言用空记数f;;B;当然,进去出不来无限运行下去是不可接受的这就要在循环体内设exit命令跳出或调用另一子程序或任务,在另一程序块中终止有限循环2满足某个条件不再迭代,其迭代次数是有限的•如果先测试条件再进入循环体叫‘当循环while_do,形式如下Ada,在无限循环体之上加while子句Exp TB:loop条件表达式为真执行循环体•如果先执行循环体再测试条件叫‘直到循环循.until,形式如下Pascal;在无限循环体之下加until子句REPEATUNTIL Exp;条件表达式为假再次执行循环体这种循环的缺点是条件不完全或条件已满足至少要执行一次所以是条件比较清楚时用它两种循环中条件表达式的变量,或进入循环被加工数据的初始化可以在循环体或子句内完成,也可以在循环外完成一般说来,这两种循环的次数难以预知,以满足条件为准这两种循环实质是一种,故有些语言只有while_do,没有do_until,每当需用do_until时,可用以下等价形式B二〉while TEXPloopUNTIL ExpB;end loop;REPEAT B;•通用有限循环general loop如果循环控制条件可以设在循环的任意位置即通用,它相当于while_do和do_until的功能复合结构化理论叫它do_while_do,其形式是FORTH BEGINBl;WHILE ExplB2;REPEAT Exp2这种结构最方便,若Bl,Exp2为空,BPwhile_doo若Expl为空,即do_until多数结构化语言没有专门的通用循环,但提供的其它机制可以实现,如下例例7-3将屏幕上输入的数求和以便打印Ada,约定当输入负数时停止function READADD returnInteger isN:Integer;SUM:Integer:=0;loopWRITEPROMPT Pleaseenteranumber andtype aCR〃;N:=GET NUMBER;exit whenN=0;SUM:=SUM+N;endloop;return SUM;end READADD;计数循环3如果已知数据结构的长度,将while子句的条件表达式量化,即给循环控制变量以初值、终值、增量,当控制变量中的值达到终值即出循环这种循环叫计数循环counted loop它是效率最高的循环,也是程o序设计语言最早采用的循环FORTRAN DOL I=EXP1,EXP2,EXP3其中I是事先声明的控制变量,整数类型,三个表达式是在此刻能求出值的整表达式用EXP1赋初值后进入循环,执行完循环体检查是否超出EXP2若未超出,则增量EXP3后执行,否则出循环以后陆续研制的语言与此大同小异,主要是在循环控制变量和表达式约定上不同几十年来一直在演变循环控制变量,最初限于非负整数,相应表达式也是整数类型FORTRAN曾经有过许可实数表达式,但未推广Ada容许所有表达式均为离散类型整、真值、字符、枚举控制变量声明,早期是全局静态变量,如从循环中跳出,控制变量值仍可引用,但如果正常出口该值总要大于或等于终值而终值本身在不同语言中也是可变的PL/1,COBOL可在循环体内改变表达式EXP2的值,FORTRAN不可这样就难于控制了所以,现代语言即使是静态变量出了循环也不能引用,Pascal的循环控制变量规定了局部于子程序,并明文规定,循环内不得改变终值和增量表达式的值虽然出了循环该值仍存在局部变量,但使用无效Ada更进一步,循环控制变量仅限于该循环,事先不用声明,给变量定义的取值范围自动作了声明,也不能在循环体内改变取值范围大小,且出了循环无效编译可查出关于增量表达式,在仅限于正整数的语言中,缺省为+1,Alogl60和Pascal容许递减,可以是负整数Ada的增量随类型,它只有正序和逆reverse序,如例7-4Ada的for循环for Iin reverse
1..N loop一I自动为整型,I=N,NT,,1for TODAYin DAYrange MON..FRI loop—TODAY自动为DAY枚举类型并取子域MON..FRI,-其增量按DAY中定义顺序一一枚举for BACKDAYin reverseDAY RANGEloop一按枚举类型DAY的逆序,即SUN,SAT,FRI,,MON—DAY RANGE是属性表达式,意即DAY类型的值域还有一个要说明的问题,是表达式EXP2在顶部求值还是在底部求值?EXP1在顶部或外部,EXP3在底部求值这都没有问题早期FORTRAN的EXP2在底部求值,以后FORTRAN-77改为顶部,。
个人认证
优秀文档
获得点赞 0