北航程序设计语言原理教材第07章
- 格式:doc
- 大小:300.50 KB
- 文档页数:18
第7章程序控制冯·诺依曼机器模型变量的时空特性对程序中求值的次序是十分敏感的。
程序员用这类语言要得到预想的计算,就要善于驾驭求值次序。
表达式的求值次序是最低层的程序控制,在前面章节中我们已经介绍过了。
在它的上层是四类控制:顺序控制、选择控制、重复(迭代)、函数或过程调用。
本章要讨论它们。
再上二层是对程序模块的控制。
包括一个程序的各模块组织以及它们与环境(软、硬件平台)的相互关系。
本章部分讨论它们。
并发控制也是一类控制,它可以在语句级,特征块和模块级实施并发控制。
涉及并发结构模型和通信机制后文第13章还要讨论。
本章只讨论顺序程序控制。
结构化程序最重要的成果是程序控制。
它使程序成为可分析的、分层结构的,程序正文和程序的执行逻辑比较一致。
同时也使编译器易于实现,尽管它已定论,在前述章节中也零散讨论某些部分。
本章对此作一系统的小结。
7.1 一般概述程序控制是控制机器执行计算的次序。
我们把表达式求值规则看作是微观的。
则最基本的控制对象是“语句”(Statement)所表征的计算。
在英语中statement一词有'陈述'的意思,易于和声明混淆,所以,有的书上主张叫“命令”(command)。
对于命令式(Imperative)语言叫命令也许更好,本书这两个词通用。
早期的高级语言脱胎于汇编语言,语句和汇编语言的一条指令对应。
它相当于程序世界的一条“指令”。
是可以独立处理的最小单元(当编译或解释时)。
程序员通过语言代码陈述的先后表达是程序最基本的顺序控制。
但是一个复杂的计算单有顺序指令是编不出来的。
汇编语言的JUMP指令在程序世界就成了GOTO命令。
它打破单一的顺序,使程序有声有色。
可以毫不夸张地说:命令式语言只要有赋值语句V∶=EXP,简单的逻辑条件IF(e)和GOTO语句就可以编出一切计算程序(输入/出除外)。
按IF条件跳过一段代码是很容易的, GOTO返回到某起始语句即可实现重复。
早期的语言都脱离不了GOTO。
GOTO和语句标号把顺序的程序代码切分得七零八落。
编译时只能把它们分成很多可连续执行的程序小块(显然,GOTO语句的前后语句不能在一块)的语句组。
这就是模块(Module)一词的来历。
对于重复执行的块单给出关键字和截止处语句标号,并引出循环域和嵌套循环的概念。
这是模块封闭性的开始:GOTO只能从循环域内转向域外,反之不行。
但当时重点还在想出方便的GOTO表示上。
单GOTO语句FORTRAN就有五种之多。
流程图的发明使得编程思路清晰,而程序代码仅仅是实现流程图的表示工具,它本身的可读性未引起足够重视。
随着程序尺寸的增长,脱离了流程图,程序正文越来越难读(见第2章图2-3示例)。
即使不太大的程序,几十个GOTO来回穿梭就成了戏称的“乱面条”程序。
难读、难调、难修改。
但当时人们醉心于精巧的设计。
终于酿成60年代初的软件危机。
自从1965年E.Dijkstra提出“GOTO语句是有害的”以来,60年代中叶在西方软件界引起一场争论,因为它动摇了赖以构成巧妙计算转移的根基。
既然它有害,那么,首先要回答,不用GOTO行不行?1966年Boehm和Jacopini回答了这个问题:任何流程图的计算逻辑都可以用顺序组、条件选择组、迭代组三种程序结构实现。
这三种结构严格一个进口一个出口。
也正因为如此可以随意嵌套(将某一对进出口置换成另一结构),构成极为复杂的程序。
用这三种构件块构造程序可完全不用GOTO语句。
这就是结构化程序设计的基本思想。
程序控制在块一级,块相对封闭,即不许有控制从块的一部分处转移到另一块内。
这样程序控制就成了组织顺序、选择、迭代的结构了。
顺序条件选择迭代图7-1 三种最基本的程序结构理论探索完成后要回答的第二个问题是用结构化程序代替非结构化程序会有哪些问题?是否一切都好?事实上,结构化程序结构确有不方便之处,至少是低效。
例如,在一计数循环中查找一个数,头几次就找到了,余下空循环不做到底是不能出来的。
保留GOTO一跳出来不是更好吗?此外,若想利用某块中的部分代码不能从需用处进入非得从头上进入,这势必要保证不用部分空执行(没有GOTO,就要增加很多不必要的代码)。
这就是是否取消GOTO值得争论的原因。
最后大家接受了Knuth(1974)对GOTO使用的折衷意见:GOTO是有害的,但有时对改善效率带来好处。
可以保留GOTO,但要加以限制使用。
只能向前转移,不能向后。
GOTO的迹线不能交叉,只能从块内转到块外,不得相反。
问题基本解决,争论的双方依然坚持各自信念,所以70年代以后发展起来的语言有保留GOTO的,如Pascal,Ada,C,但不提倡使用。
有完全取消GOTO 的,如Modula,Adison等等。
有的保留GOTO的积极作用限制GOTO的副效应,把它们改头换面变为比较安全的顺序控制器(sequencer),详见后文。
结构化程序除了顺序、条件选择、迭代三种最基本控制而外,子程序调用也是一种重要的控制。
虽然在子程序体中依然是三种基本控制结构的复合,子程序调用本身是显式地控制抽象。
多次子程序调用和返回,构成程序的深层嵌套。
对于非命令式语言,如纯函数式语言,顺序控制并不十分重要。
它控制计算更多的依靠嵌套。
7.2 顺序控制顺序控制在程序结构上是一组可顺序执行的简单语句,如果每一个顺序成分可用一顺序结构置换,顺序结构可形式地表示为:S1; S2进一步扩展可为:S1; S2; ...Sn其中Si为简单语句(命令)。
一般过程语言有哪些简单语句呢?我们用最丰富的Ada简单语句作解释。
简单_语句::=空_语句|赋值_语句 | 过程调用_语句|goto_语句 | 入口调用_语句|出口_语句 | 返回_语句|引发_语句 | 夭折_语句|延迟_语句 | 代码_语句其中空_语句,赋值_语句,代码_语句不影响控制和转移,代码语句是某些信息要用机器(或汇编)码。
延迟语句为控制同步,指定在原地停留的时间,但无控制转移。
连续的赋值是最基本的运算。
goto语句已如前述,可以往前、往后跳过任意行代码。
当然它和语句标号要配合。
标号的表示法各语言不一,有用数字,有用字符串的。
Ada用<<字符串>>。
exit(出口)语句,raise(引发)语句,abort(夭折)语句,都是顺序控制符。
exit将程序控制跳到所在块的末端,或由它指明的外嵌套块的末端。
C语言中与其相当的是break。
raise是引发异常。
当程序执行到此句(一般是程序员预感要出异常设在此处)时,跳到有exception关键字处执行异常处理段(一般在本程序块末尾),且不返回。
异常引发规则比较复杂,详见本章7.5节。
abort是强行夭折即就地停止,有的语言是HALT命令。
这类就地停止和暂停命令在FORTRAN 早已有过。
FORTRAN的STOP和END分工是一为执行停止,一为代码结束。
由于它把程序正文表示和执行逻辑分开。
任设STOP为多出口。
与结构化宗旨有悖,在以后的版本中取消了。
FORTRAN 的PAUSE为暂停语句,它中止程序执行,以便程序员中途作些调整,调试程序,而且必须重按回车才能恢复运行。
这些早期单机单用户,惜机时如金时代的便于调试的语句,以后都取消了。
完全可以设“断点”,追踪排错解决。
恢复abort和HALT是新一轮并发程序和增加了异常处理后的要求。
强行终止程序执行一般是信息不足,事先考虑的异常处理段不足以处理已发生的异常。
而进一步运行又无意义,只好终止程序的执行。
一般终止前应将当前信息尽可能收集、显示出来,以便调试员分析。
入口调用和子程序调用相似,前者是专用于并发程序的任务调用。
也要作参数匹配。
每当执行到此句,一个任务被激活,任务体开始执行。
它对程序控制转移到另一程序单元作用是一样的。
同样,return是显式指明执行到此返回至调用点。
因为返回是同一点,多个return一般都是允许的(违反一出口的规定吗?)。
早期子程序过程必有return,而现代语言则不一定(为什么)?程序总是按程序正文自上至下,自左至右地(隐含)顺序执行的。
表达式求值规则和顺序控制器是对这个隐含执行的“修正”,从而达到控制计算的目的。
7.3 条件选择控制计算机“聪明”最基本的基础是会根据条件选择它应执行的代码。
所有的程序设计语言都离不开条件控制。
7.3.1 结构式条件控制简单的条件语句IF(e),根据逻辑表达式的真假值在程序中产生分枝。
它本来就是想做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 E1 then if E2 then S1 else S2它可以解释为:(E1=true)Λ(E2=false) 执行S2,else属于内部if,也可以解释为:(E1=false) 执行S2,else属于外部if即不管E1为真假S2均可能执行。
原因是可以画成多个语法树,它有二义性。
解决这个问题是很容易的,加上语句括号DO_END(PL/1),BEGIN_END(Pascal)就可以:if E1 then begin if E2 then S1 else S2 endif E1 then begin if E2 then S1 end else S2它的简化形式(FORTRAN-77, Ada)是:if E1 then if E2 then S1 endif else S2 endifif E1 then if E2 then S1 else S2 endif endif└───就近匹配──┘语句括号既使语义清晰又可以封闭块,所以,Pascal,C的程序员要指明块时(两个语句以上),都要用语句括号begin_end或 { }。
既然不要goto,早期的流程图也可以结构化。
与此对应的选择结构有nassi_Schneiderman 式流程图。
每个语句和复合语句都是块连接,如图7-2所示。
块中分成T-,F-子句分别画成T-域和F-域。
可随意嵌套(将-S块又扩充为选择结构)。
图7-2结构选择的Nassi_Schneiderman7.3.2 case和switch在图7-2所示结构选择中是连续嵌套的选择结构对应的程序结构是:IF exp1 THEN IF exp1 THENST1 ST1ELSEIF exp2 THEN ELSEST2 IF exp2 THENELSEIF exp... ST2... ELSEIF exp ...ELSE ELSESF3 SF3ENDIF ENDIF左右两表示法语义是一样的,但左边出了新关键字ELSEIF。