北航程序设计语言原理教材第07章Word格式文档下载.docx

上传人:b****3 文档编号:16720203 上传时间:2022-11-25 格式:DOCX 页数:24 大小:76.39KB
下载 相关 举报
北航程序设计语言原理教材第07章Word格式文档下载.docx_第1页
第1页 / 共24页
北航程序设计语言原理教材第07章Word格式文档下载.docx_第2页
第2页 / 共24页
北航程序设计语言原理教材第07章Word格式文档下载.docx_第3页
第3页 / 共24页
北航程序设计语言原理教材第07章Word格式文档下载.docx_第4页
第4页 / 共24页
北航程序设计语言原理教材第07章Word格式文档下载.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

北航程序设计语言原理教材第07章Word格式文档下载.docx

《北航程序设计语言原理教材第07章Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《北航程序设计语言原理教材第07章Word格式文档下载.docx(24页珍藏版)》请在冰豆网上搜索。

北航程序设计语言原理教材第07章Word格式文档下载.docx

GOTO和语句标号把顺序的程序代码切分得七零八落。

编译时只能把它们分成很多可连续执行的程序小块(显然,GOTO语句的前后语句不能在一块)的语句组。

这就是模块(Module)一词的来历。

对于重复执行的块单给出关键字和截止处语句标号,并引出循环域和嵌套循环的概念。

这是模块封闭性的开始:

GOTO只能从循环域内转向域外,反之不行。

但当时重点还在想出方便的GOTO表示上。

单GOTO语句FORTRAN就有五种之多。

流程图的发明使得编程思路清晰,而程序代码仅仅是实现流程图的表示工具,它本身的可读性未引起足够重视。

随着程序尺寸的增长,脱离了流程图,程序正文越来越难读(见第2章图2-3示例)。

即使不太大的程序,几十个GOTO来回穿梭就成了戏称的“乱面条”程序。

难读、难调、难修改。

但当时人们醉心于精巧的设计。

终于酿成60年代初的软件危机。

自从1965年E.Dijkstra提出“GOTO语句是有害的”以来,60年代中叶在西方软件界引起一场争论,因为它动摇了赖以构成巧妙计算转移的根基。

既然它有害,那么,首先要回答,不用GOTO行不行?

1966年Boehm和Jacopini回答了这个问题:

任何流程图的计算逻辑都可以用顺序组、条件选择组、迭代组三种程序结构实现。

这三种结构严格一个进口一个出口。

也正因为如此可以随意嵌套(将某一对进出口置换成另一结构),构成极为复杂的程序。

用这三种构件块构造程序可完全不用GOTO语句。

这就是结构化程序设计的基本思想。

程序控制在块一级,块相对封闭,即不许有控制从块的一部分处转移到另一块内。

这样程序控制就成了组织顺序、选择、迭代的结构了。

理论探索完成后要回答的第二个问题是用结构化程序代替非结构化程序会有哪些问题?

是否一切都好?

事实上,结构化程序结构确有不方便之处,至少是低效。

例如,在一计数循环中查找一个数,头几次就找到了,余下空循环不做到底是不能出来的。

保留GOTO一跳出来不是更好吗?

此外,若想利用某块中的部分代码不能从需用处进入非得从头上进入,这势必要保证不用部分空执行(没有GOTO,就要增加很多不必要的代码)。

这就是是否取消GOTO值得争论的原因。

最后大家接受了Knuth(1974)对GOTO使用的折衷意见:

GOTO是有害的,但有时对改善效率带来好处。

可以保留GOTO,但要加以限制使用。

只能向前转移,不能向后。

GOTO的迹线不能交叉,只能从块内转到块外,不得相反。

问题基本解决,争论的双方依然坚持各自信念,所以70年代以后发展起来的语言有保留GOTO的,如Pascal,Ada,C,但不提倡使用。

有完全取消GOTO的,如Modula,Adison等等。

有的保留GOTO的积极作用限制GOTO的副效应,把它们改头换面变为比较安全的顺序控制器(sequencer),详见后文。

结构化程序除了顺序、条件选择、迭代三种最基本控制而外,子程序调用也是一种重要的控制。

虽然在子程序体中依然是三种基本控制结构的复合,子程序调用本身是显式地控制抽象。

多次子程序调用和返回,构成程序的深层嵌套。

对于非命令式语言,如纯函数式语言,顺序控制并不十分重要。

它控制计算更多的依靠嵌套。

7.2顺序控制

顺序控制在程序结构上是一组可顺序执行的简单语句,如果每一个顺序成分可用一顺序结构置换,顺序结构可形式地表示为:

S1;

S2

进一步扩展可为:

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嵌套时,有:

ifE1thenifE2thenS1elseS2

它可以解释为:

(E1=true)Λ(E2=false)执行S2,else属于内部if,也可以解释为:

(E1=false)执行S2,else属于外部if

即不管E1为真假S2均可能执行。

原因是可以画成多个语法树,它有二义性。

解决这个问题是很容易的,加上语句括号DO_END(PL/1),BEGIN_END(Pascal)就可以:

ifE1thenbeginifE2thenS1elseS2end

ifE1thenbeginifE2thenS1endelseS2

它的简化形式(FORTRAN-77,Ada)是:

ifE1thenifE2thenS1endifelseS2endif

ifE1thenifE2thenS1elseS2endifendif

└───就近匹配──┘

语句括号既使语义清晰又可以封闭块,所以,Pascal,C的程序员要指明块时(两个语句以上),都要用语句括号begin_end或{}。

既然不要goto,早期的流程图也可以结构化。

与此对应的选择结构有nassi_Schneiderman式流程图。

每个语句和复合语句都是块连接,如图7-2所示。

块中分成T-,F-子句分别画成T-域和F-域。

可随意嵌套(将-S块又扩充为选择结构)。

7.3.2case和switch

在图7-2所示结构选择中是连续嵌套的选择结构对应的程序结构是:

IFexp1THENIFexp1THEN

ST1ST1

ELSEIFexp2THENELSE

ST2IFexp2THEN

ELSEIFexp...ST2

...ELSE

IFexp...

ELSEELSE

SF3SF3

ENDIFENDIF

左右两表示法语义是一样的,但左边出了新关键字ELSEIF。

这是在FORTRAN-IV到FORTRAN-77结构化改版时增加的,增加后显得自然方便,只是在“都不”满足条件时执行最后一个ELSE。

不用费心记对应是那一层。

ELSEIF在Ada中更成为可区分的elsif,和elseif是两种表示。

嵌套IF结构的执行,永远是执行一组代码就跳到endif。

对于不同条件,if_then_elseif...else结构方便,清晰。

然而,对于同一条件表达式而多次判断其值的嵌套IF结构,这种表示显得累赘,且多次错缩进编排,程序反倒歪斜。

于是多数结构化语言提供case选择。

两种语言的分情况语句的表示示例如下:

caseExpisswitch(exp)

whenv1=>

casev1:

whenv2=>

break;

...casev2:

whenvm|vn=>

Sn;

whenothers=>

St;

default:

endcase;

Ada的case语句C的switch语句

这两种表示法略有差异,大体一致。

Ada的Exp不限于真值表达式(只两种情况),可以是任何离散类型表达式(C限于整数)。

根据取值Vi的情况跳到对应的case标号那个子句Si执行。

Ada是执行完子句跳到endcase,而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=b<

c?

b:

cif(b<

c)a=b;

elsea=c;

条件表达式条件语句

一个语言并没有必要既提供条件表达式又提供条件语句。

事实上,任何条件表达式描述的计算条件语句都可以做到,只是更臃杂一些。

但是条件表达式返回的是一个值,它可以做函数的实参变元,在封闭的程序中继续使用。

而条件语句不能作为实参变元,它与程序的其它部分通信只能通过环境。

事实上,赋值就在改变环境(的状态)。

为此,函数式语言,例如LISP,就用了许多括号封闭各层嵌套调用(ML用文字括号如let...in等)。

程序设计风格也是嵌套,没有或不强调顺序子句。

可以这么说,命令式语言大量依赖顺序命令,纯函数式语言完全依赖写嵌套函数调用。

可惜LISP受到早期过程语言高效的影响也包含了rplaca(破坏性赋值)函数和prog控制结构。

程序员可以使用变量并列出顺序执行的表达式。

这样一来就没有纯嵌套和纯顺序的语言了。

命令式语言主要是顺序赋值,但也有嵌套和递归函数调用,参数结合,甚至像C语言那样有条件表达式。

而LISP主要是嵌套,但也有赋值,和顺序执行。

ML,Miranda,FP力图回复纯函数式,以摆脱变量时空特性的影响。

7.4迭代控制

迭代一般用于处理同构的、聚集数据结构的重复操作,诸如数组、表、文件。

对于命令式语言迭代一般是显式的循环,例如上节讨论的结构化语言的while_do。

对于函数式语言迭代一般是隐式的,如LISP的map函数,它通常返回一个不断延长的表。

结构化语言的设计者虽然接受任何迭代都可以转为while_do表示的结构化程序理论,但为了便利于表达,迭代的表示法多种多样,赛过50年代FORTRAN的GOTO表示法,即使是同一种表示法其表达的灵活性也是空前的。

C语言有以下例子:

例7-2用for循环计算表中元素之和

设表list是一简单结构,一个成分是值value,另一个成分是指向下一表元素的指针next。

可以有以下六种写法:

[1]先赋sum初值进入正规for循环:

sum=0;

for(current=list;

current!

=NULL;

current=current->

next)

sum+=current->

value;

//循环控制变量current就用表list

[2]sum在循环内赋初值:

for(current=list,sum=0;

current!

=NULL;

current=current->

next)

sum+=current->

value

[3]循环控制变量可以在for以外赋初值,增量在体内赋值:

current=list,sum=0;

for(;

value,current=current->

next;

//用'

,'

号连接的两语句成为一个命令表达式

[4]循环条件为空,用条件表达式完成:

current=list,sum=0;

for(;

;

if(current==NULL)break;

elsesum+=current->

value,current->

//不提倡此种风格

[5]完全不用循环体,全部在循环条件中完成

for(current=List,sum=0;

(current=current->

next)!

value);

//效率最高.但list不能为NULL表

[6]逻辑混乱,也能正确计算:

for(;

current=current->

value)

//最坏的风格,list也不能为NULL表

这个例子说明语言的语法规则的灵活性,C语言的哲学是只要不给实现带来过多开销,尽量灵活,不管程序员如何写,说得过去就行,例如,[4]中的if的T-子句是语句,F-子句是表达式,整个既是命令表达式也是条件命令。

这种过多的灵活性也带来排错调试的困难。

7.4.1显式迭代控制

控制迭代可以在一段循环代码开始处设条件,也可以在执行一次后设条件,决定是否重复。

也可以规定次数,虽然都可以用最基本的while_do仿真。

但各有特点,语言选用也不完全一致。

(1)无限循环

在顺序式命令语言中无限循环被认为是死循环,早期语言均没有,对于并发程序,为了随时接受另一进程发来的信息,它要处于活动的运行状态,就必须用无限循环。

以下是Ada的无限循环及其对应的结构图:

名字name是可选的,有的语言用关键字BEGIN_REPEAT(FORTH83)表示,C语言用空记数f(;

;

)B;

当然,进去出不来无限运行下去是不可接受的。

这就要在循环体内设exit命令跳出(或调用另一子程序或任务,在另一程序块中终止)。

(2)有限循环

满足某个条件不再迭代,其迭代次数是有限的。

·

如果先测试条件再进入循环体叫‘当循环‘(while_do),形式如下(Ada),在无限循环体之上加while子句:

条件表达式为真执行循环体。

·

如果先执行循环体再测试条件叫‘直到循环‘(do_until),形式如下(Pascal);

在无限循环体之下加until子句:

条件表达式为假再次执行循环体。

这种循环的缺点是条件不完全或条件已满足至少要执行一次。

所以是条件比较清楚时用它。

两种循环中条件表达式的变量,或进入循环被加工数据的初始化可以在循环体或子句内完成,也可以在循环外完成。

一般说来,这两种循环的次数难以预知,以满足条件为准。

这两种循环实质是一种,故有些语言只有while_do,没有do_until,每当需用do_until时,可用以下等价形式:

REPEATB;

B:

=>

whileTEXPloop

UNTILExpB;

endloop;

通用有限循环(generalloop)

如果循环控制条件可以设在循环的任意位置(即通用),它相当于while_do和do_until的功能复合。

结构化理论叫它do_while_do,其形式是(FORTH):

这种结构最方便,若B1,Exp2为空,即while_do。

若Exp1为空,即do_until。

多数结构化语言没有专门的通用循环,但提供的其它机制可以实现,如下例:

例7-3将屏幕上输入的数求和以便打印(Ada),约定当输入负数时停止。

functionREAD_ADDreturnIntegeris

N:

Integer;

SUM:

Integer:

=0;

loop

WRITE_PROMPT("

PleaseenteranumberandtypeaCR"

);

=GET_NUMBER;

exitwhenN<

=SUM+N;

endloop;

returnSUM;

endREAD_ADD;

(3)计数循环

如果已知数据结构的长度,将while子句的条件表达式量化,即给循环控制变量以初值、终值、增量,当控制变量中的值达到终值即出循环。

这种循环叫计数循环(countedloop)。

它是效率最高的循环,也是程序设计语言最早采用的循环(FORTRAN):

DOLI=EXP1,EXP2,EXP3

其中I是事先声明的控制变量,整数类型,三个表达式是在此刻能求出值的整表达式。

用EXP1赋初值后进入循环,执行完循环体检查是否超出EXP2。

若未超出,则增量EXP3后执行,否则出循环。

以后陆续研制的语言与此大同小异,主要是在循环控制变量和表达式约定上不同。

几十年来一直在演变。

循环控制变量,最初限于非负整数,相应表达式也是整数类型。

FORTRAN曾经有过许可实数表达式,但未推广。

Ada容许所有表达式均为离散类型(整、真值、字符、枚举)。

控制变量声明,早期是全局静态变量,如从循环中跳出,控制变量值仍可引用,但如果正常出口该值总要大于或等于终值。

而终值本身在不同语言中也是可变的(PL/1,COBOL可在循环体内改变表达式EXP2的值,FORTRAN不可)。

这样就难于控制了。

所以,现代语言即使是静态变量出了循环也不能引用,Pascal的循环控制变量规定了局部于子程序,并明文规定,循环内不得改变终值和增量表达式的值。

虽然出了循环该值仍存在(局部变量),但使用无效。

Ada更进一步,循环控制变量仅限于该循环,事先不用声明,给变量定义的取值范围自动作了声明,也不能在循环体内改变取值范围大小,且出了循环无效(编译可查出)。

关于增量表达式,在仅限于正整数的语言中,缺省为+1,Alogl60和Pascal容许递减,可以是负整数。

Ada的增量随类型,它只有正序和逆(reverse)序,如:

例7-4Ada的for循环

forIinreverse1..Nloop

--I自动为整型,I=N,N-1,...,1

forTODAYinDAYrangeMON..FRIloop

--TODAY自动为DAY(枚举)类型并取子域MON..FRI,

--其增量按DAY中定义顺序一一枚举

forBACKDAYinreverseDAY'

RANGEloop

--按枚举类型DAY的逆序,即SUN,SAT,FRI,...,MON

--DAY'

RANGE是属性表达式,意即DAY类型的值域

还有一个要说明的问题,是表达式EXP2在顶部求值还是在底部求值?

EXP1在顶部或外部,EXP3在底部求值这都没有问题。

早期FORTRAN的EXP2在底部求值,以后FORTRAN-77改为顶部,这样一开始条件不满足就不要进入循环。

(4)迭代元素

1963年美国Galler和Fischer对FOR循环提出迭代元素(Iterationelement)概念,以后C语言的发明人D

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 自然景观

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1