上章讨论的控制结构的特点是通过判别条件来对结构内的块进行选择,所对应的算法结构,最简单的例子,就是解一元二次方程,在输入方程所以的参数值之后,需要首先计算一个判别式,然后根据判别式的值,再选择使用哪个公式,也就是计算的途径,才能够给出最终的解。
在本章所讨论的控制结构的特点则是针对结构内的块进行多次的重复运算,每完成一次运算,都判别一下是否需要把此次运算结果作为输入,再进行一次运算。这种控制结构所对应的算法结构,一个最简单的例子,就是求级数的部分和。
我们知道对于具有通项表达式的级数,求它的部分和的每一项,总是需要进行同样的运算过程,如果使用按照序列形式排列的程序结构,那么需要计算多少项,就需要写下多少条语句,把它们顺序排列下来,才能做到程序走一遍即完成计算。这样的算法设计显然是没有利用运算过程里所表现的循环结构,如果使用一种控制结构与循环过程对应,让程序的运行能够重复表示通项公式的表达式计算,就能够用一个表达式代替所有项的表达式,显然更加合理。
【例11-1】 设有一个级数:
如果要求级数在N=K时的值,如果一定要使用序列结构的程序,那么在程序当中肯定会出现如下K个表达式顺序排列的情形:
…
I=1
SUM=1/I**3
I=2
SUM=SUM+1/I**3
I=3
SUM=SUM+1/I**3
…
I=K
SUM=SUM+1/I**3
由于这K个表达式是一样的,因此如果使用如下的一个控制结构,只需要使用一个表达式赋值语句,就可以表示整个循环运算过程:
…
SUM=0.0
DO I=1,K
SUM= SUM+1/I**3
END DO
上面的控制结构,就是本章所要讨论的DO结构。
实际上这是一种基本的计算过程,为很多重要算法的实现提供了基础。
FORTRAN语言提供用来进行循环控制的主要结构就是DO结构,因此本章主要讨论的就是DO结构。最后还会简略的讨论有关分支转移的实现问题,尽管它们不属于循环控制,但是由于在现代结构性编程风格的要求下,这种分支转移是一种过时的方式,因此简略地附加在本章后面。
DO结构包含0个或多个语句或结构,它们在DO结构的其他部分的控制下进行重复运行。这些被重复运算的语句或结构构成一个循环,DO结构控制了该循环的运行次数。
DO结构的运行分为如下步骤:
● 如果DO结构由一个DO变量控制,那么决定循环次数的该表达式的值首先需要计算出来;
● 然后决定循环部分是否运行;
● 如果运行循环部分,则完成循环后,更新DO变量,再进入第二个步骤;如果不需要运行循环部分,则退出该DO结构,进入程序的后续部分。
其中对于DO结构的循环部分的控制方式有3种:
● 利用一个循环变量从初始DO语句开始,以确定的方式增长(也可能是负方向增长), 该变量作为一个循环计数器,它的取值变化的次数标记了循环的次数;
● 利用WHERE条件;
● 使用简单DO结构,或者称为“永远DO”。显然这种方式需要一个可执行语句来终止DO结构,例如EXIT语句,可以用来退出循环。
从DO结构的整体构造来看,DO结构分为两种形式:
● DO结构块;
DO结构本身构成一个块结构,总是使用一个END DO语句或CONTINUE语句来终止DO结构的运行。
● 非块DO结构。
非块DO结构本身不构成一个块状结构,它或者使用一个作用语句或结构来终止
该DO结构的运行,或者与其他DO结构共享一个终止语句。
这两种形式具有相同的功能,都可以使用DO WHERE与“永远DO”的循环形式,但是非块DO结构属于早期FORTRAN版本的遗留物,是在还不重视结构化编程的时代的产物,因此是过时的表达方式,现代FORTRAN语言不提倡使用。
下面是一个DO结构块的例子。
【例11-2】
DO I=1,N
SUM=SUM+X(I)
END DO
下面是执行与上例同样的计算任务的非块DO结构的例子。
【例11-3】
DO 100 I=1,N
100 SUM=SUM+X(I)
DO结构块就是使用不与其他DO结构共享的END DO语句或CONTINUE语句作为终止语句的DO结构。它的一般句法形式(R817)为:
[do-construct-name:] DO [label][loop-control]
[execution-part-construct]…
[label] END DO
其中循环控制(loop-control)的句法形式(R821)为:
[ ,]scalar-integer-variable-name = &
scalar-integer-expression,scalar-integer-expression &
[ ,scalar-integer-expression]
[ ,]WHILE(scalar-logical-expression)
其中END DO语句的句法形式(R824)为:
END DO [do-construct-name]
CONTINUE
其中在可选的结构名称加关键词DO后面的语句称为DO语句;关键词END DO后面的语句称为END DO语句;关键词CONTINUE后面的语句称为CONTINUE语句。
DO结构的一般规则如下:
● DO结构里循环控制里的DO变量,必须是整型的标量命名变量。
● 整个DO结构块如果有一个结构名称,那么它必须同时出现在DO语句和END DO语句里面,而不能只出现在其中之一里面。
● 如果DO语句里面不包含标签,那么END DO语句里面也不能包含标签;如果DO语句里面包含标签,那么END DO语句必须使用同样的标签。注意DO结构块里的终止语句绝对不能与其他DO结构共享,因此END DO语句的标签绝对不能在别的地方被引用。如果终止语句其他DO结构共享了,则属于非块DO结构。
● DO结构里面可选的标签既可以使用自由源码形式也可以使用固定源码形式。
下面给出一些不同形式的DO结构的例子。
【例11-4】
SUM=0.0
DO I=1,N
SUM=SUM+(-1)**N*(1/N**2)
END DO
这个例子里面的DO变量是I。
【例11-5】
FOUND=.FALSE.
I=0
DO WHILE(.NOT.FOUND.AND.I<LIMIT)
IF(KEY==X(I))THEN
FOUND=.TRUE.
ELSE
I=I+1
END IF
END DO
这个例子使用了WHERE语句。
【例11-6】
NO_ITERS=0
DO
下面的FU和FU_PRIME都是函数
X1=X0-FU(X0)/FU_PRIME(X0)
IF ( ABS (X1 - X0 ) < SPACING ( X0 ) .OR. &
NO_ITERS > MAX_ITERS ) EXIT
X0 = X1
NO_ITERS=NO_ITERS+1
END DO
这个DO结构里面使用 IF语句。
【例11-7】
INNER_PROD=0.0
DO 100 I=1,10
INNER_PROD=INNER_PROD+X(I)*Y(I)
100 CONTINUE
这个例子里面使用了CONTINUE语句。
【例11-8】
LOOP: DO I=1 , N
Y(I)=A*X(I)+Y(I)
END DO LOOP
在这个例子里面给出了DO结构名称。
非块DO结构的终止语句总是使用标签,并且采取如下两种情形:
● 一个非块DO结构的终止语句是与其他的DO结构共享的;
● 非块DO结构的终止语句本身是一个结构或作用语句,而不是END DO语句或CONTINUE语句。
具有这样的终止语句的DO结构,就不能形成一个块结构,这导致它是一种过时的表述方式。
非块DO结构的句法形式(R826)为:
action-terminated-do-construct
outer-shared-do-construct
其中作用语句终止DO结构(action-terminated-do-construct)的句法形式(R827)为:
[do-construct-name:]DO label [loop-control]
[execution-part-construct]…
label action-statement
其中可外部共享DO结构(outer-shared-do-construct)的句法形式(R830)为:
[do--construct-name:]DO label [loop-control]
[execution-part-construct]…
label shared-termination-do-construct
其中可共享终止DO结构(shared-termination-do-construct)的句法形式(R831)为:
outer-shared-do-construct
inner-shared-do-construct
其中可内部共享DO结构(inner-shared-do-construct)的句法形式(R832)为:
[do-construct-name:]DO label [loop-control]
[execution-part-construct]…
label action-statement
其中用来退出作用语句终止DO结构(action-terminated-do-construct)的作用语句称为终止DO作用语句;用来退出可内部共享DO结构(inner-shared-do-construct)的作用语句称为终止DO共享语句;所有这些用来终止非块DO结构的语句或结构形式,包括终止DO作用语句,终止DO共享语句,终止DO共享结构,都称为DO终止,或DO结构的终止语句。
非块DO结构的一般规则如下:
● 一个终止DO作用语句不能是任何形式的如下语句:
CONTINUE语句;GO TO语句;RETURN语句;STOP语句;EXIT语句;CYCLE语句;END语句。
● 终止DO作用语句和相应的DO语句必须具有相同的标签。
● 一个终止DO共享语句不能是任何形式的如下语句:
GO TO语句;RETURN语句;STOP语句;EXIT语句;CYCLE语句;END语句。
● 终止DO共享语句和相应的所有共享它的DO语句必须具有相同的标签。
● DO结构里面可选的标签既可以使用自由源码形式也可以使用固定源码形式。
下面给出一些不同形式的非块DO结构的例子:
【例11-9】
PROD=1.0
DO 20 I=1,N
20 PROD=PROD*P(I)
【例11-10】
DO 20 I=1,N
DO 20 J=1,N
20 HILBERT(I,J)=1.0/REAL(I+J)
这个例子里面包含DO语句的嵌套结构。
【例11-11】
FOUND=.FALSE.
I=0
DO 20 WHILE(.NOT.FOUND.AND.I<LIMIT)
I=I+1
20 FOUND=KEY==X(I)
这个例子里面使用了WHERE语句。
【例11-12】:
DO 20 I=1,N
DO 20 J=I+1,N
A=B(I,J);B(I,J)=B(J,I);B(J,I)=A
20 CONTINUE
这个例子里面使用了CONTINUE语句。
所谓DO结构的范围是指从DO语句到该DO结构的终止语句的所有语句。
在DO结构的范围内部可以包含其他类型的结构,例如IF结构,CASE结构,以及其他DO结构,其中任何一个内部结构都必须是完整地包含在另一个结构之中,而不能有各个结构相互交错的情形。
如果在一个DO结构里面包含了其他DO结构,那么该DO结构称为嵌套DO结构。
嵌套的DO结构共享一个终止语句是允许的,但是属于即将淘汰的语法形式,因此尽量不要使用。
从一个DO结构的外部分支到它的内部某个语句是绝对禁止的。
一个DO结构可以处于激活状态,也可以处于灭活状态。
当一个DO结构被运行时,就是处于激活状态。
当一个DO结构处于如下几种情形时,就是处于灭活状态:
● 在检测条件时循环计数器取值为0;
● 在检测条件时WHILE条件为假;
● 执行了EXIT语句,导致从DO结构退出;
● 执行了CYCLE语句;
● 出现一个指向DO结构外部的控制转移;
● 执行了RETURN语句;
● 任何其他原因导致程序终止。
由于DO结构具有非常不一样的三种形式,因此DO结构的运行也具有三种形式:
● 带循环计数器的DO结构的运行;
● DO WHILE结构的运行;
● 简单DO结构的运行。
所有这些运行形式都可以包含能够导致程序不按照语句顺序执行的可执行语句,当然也可以包含导致DO结构运行终止的语句。
下面分别予以说明。
在这种DO结构的形式里面,循环计数器控制了循环部分执行的次数。
带循环计数器的DO结构的句法形式(R818)为:
DO [label] [ ,] do-variable=expression1,expression2[ ,expression3]
其中的DO变量与相应的表达式都必须是整型。
下面是这样的不同形式的DO语句的一个例子。
【例11-13】
DO 100 I=0,N
DO,I=-N,N
DO J=N,1,-1
所谓循环计数器是用来计量DO结构里面的循环部分的循环运行次数的。
循环计算器里面包括三个表达式,这些表达式经过计算后得到的结果必须转换为DO变量的类型,假设分别为e1,e2,e3,(其中e3不能是0,但可以省略,而取默认值1。)它们的含义为:
● e1表示DO变量的初始值;
● e2表示DO变量的终止值;
● e3作为可选项表示DO变量的变化的步长,默认值取1。
那么循环的次数可以由下面的公式得到:
MAX((e2-e1+e3)/e3,0)
注意在下面的情形下导致循环计算器的取值为0,也就导致DO结构灭活:
● e1>e2并且e3>0;
● e1<e2并且e3<0。
然后通过循环计算器的控制,DO结构的运行步骤如下:
1. DO变量的初始值设置为e1;
2. 检测循环计数器,如果取值为0,则退出DO结构;
3. 如果循环计数器的取值不是0,则DO结构的范围得到执行。
这时循环计数器的值减1,而DO变量增长e3。
4. 重复步骤2和3,一直到循环计数器的值为0。
注意在整个DO结构终止后,DO变量的取值保持为相应的循环计数器值为0时的值。
在DO结构的范围的整个运行过程中,DO变量不能被重定义,也不能被去定义。
在DO结构的运行过程当中,决定循环参数的表达式里面的变量的变化不影响循环计数器,每次进入DO结构,循环计数器的值都是固定的。
下面的示意图11-1表示了带有循环计数器的DO结构的运行流:
图11-1 带有循环计数器的DO结构的运行流
【例11-14】 下面的例子运行了10次
N=10
SUM=0.0
DO 10 I=1,N
SUM=SUM+A(I)
N=N+1
10 CONTINUE
运行之后,I=11,N=20
【例11-15】 下面的内部循环运行了10次:
X=20
DO 10 I=1,2
DO 9 J=1,5
X=X+1.0
9 CONTINUE
10 CONTINUE
运行整个DO结构之后,I=3,J=6,X=30。
如果其中的第二个DO语句改为:
DO 9 J=5,1
那么其中的内部DO结构根本就不会运行,而X取值仍然是20,J=5,I=3。
DO WHILE结构的循环控制不是通过一个整型变量来给出循环的次数,而是通过对一个取逻辑型值的表达式的运算,来获得进行循环的条件。
关键词DO WHILE后接逻辑型表达式的语句称为DO WHILE语句。DO WHILE语句的句法形式为:
DO [label ] [ ,]WHILE [scalar-logical-expression]
DO WHILE语句的例子:
DO 100 WHILE(X(I)/=0)
DO,WHILE(.NOT.FOUND)
DO WHILE(y>=0.01)
DO WHILE结构的运行非常简单:首先计算逻辑表达式,如果为真,则运行循环部分;如果为假,则退出该DO结构。
【例11-16】
SUM=0.0
I=0
DO WHILE (I<5)
I=I+1
SUM=SUM+I
END DO
上面的循环执行5次,最后得到SUM=15.0,I=5。
也可以在DO结构里面不使用任何循环控制,而是直接使用终止语句,即DO范围里面的语句总是循环执行,除非DO范围里面的某条终止语句在一定条件下被执行。
简单DO结构的一般形式就是:
DO [label ]
【例11-17】
DO
READ*,DATA
IF(DATA<0) STOP
CALL PROCESS(DATA)
END DO
在这个例子里面,一直到DATA读入一个负值,其中的DO范围才停止循环,并退出DO结构。
把上面的例子改写为带有标签的形式,如下:
DO 15
READ*,DATA
IF(DATA<0) STOP
CALL PROCESS(DATA)
15 CONTINUE
在DO结构范围内部,可以使用两个专门的语句来改变正常的运行顺序,它们是:
● EXIT语句;
● CYCLE语句。
当然可以用来在DO范围内改变正常运行顺序的语句,并不只是这两个语句,还包括一些分支语句,例如RETURN语句,STOP语句等,但是这两个语句是专门用于DO结构内部的。而那些分支语句则没有这个限制。
下面分别说明这两个语句。
EXIT语句直截了当地导致程序退出DO结构,DO结构里面的任何其他语句都不再被执行。
EXIT语句既可以在DO结构块里面使用,也可以在非块DO结构里面使用,但是它本身不能充当非块DO结构里面的终止DO作用语句和终止DO共享语句。
EXIT结构的句法形式(R835)为:
EXIT[do-construct-name]
注意EXIT语句必须是在一个DO结构内部。
如果EXIT语句具有一个结构名称,那么该EXIT语句必须是在同名DO结构的内部;当该EXIT语句被执行的时候,该同名DO结构就被终止,同时,任何包含了该EXIT语句的DO结构以及包含在该同名DO结构里面的DO结构全都被终止。
如果EXIT语句不带结构名称,那么只有所有包含EXIT语句的结构之中最接近EXIT语句的DO结构被终止。
【例11-18】
LOOP10:DO
…
IF(TEMP==INDEX)EXIT LOOP10
…
END DO LOOP10
上例当中的EXIT语句带有结构名称,那么当IF语句里面的条件被满足时,就运行EXIT语句,也就退出整个LOOP10结构。
上面的EXIT语句是完全地退出整个DO结构,而CYCLE语句则只是中断已经完成的循环,使用可能更新过的循环计数器以及DO变量,再次启动循环部分的循环计算过程。
CYCLE语句既可以在DO结构块里面使用,也可以在非块DO结构里面使用,但是它本身不能充当非块DO结构里面的终止DO作用语句和终止DO共享语句。
如果CYCLE语句在非块DO结构里面使用,那么非块DO结构里面的终止DO作用语句和终止DO共享语句将不会得到执行。
CYCLE语句的句法形式(R834)为:
CYCLE[do-construct-name]
注意CYCLE语句必须是在一个DO结构内部。
如果CYCLE语句具有一个结构名称,那么该CYCLE语句必须是在同名DO结构的内部;当该CYCLE语句被执行的时候,该同名DO结构就被中断,同时,任何包含了该CYCLE语句的DO结构以及包含在该同名DO结构里面的DO结构全都被终止。
如果CYCLE语句不带结构名称,那么只有所有包含CYCLE语句的结构之中最接近CYCLE语句的DO结构被中断。
CYCLE语句可以用于任意形式的DO语句;在循环控制条件许可的情形下,就可以启动下一个循环的开始。
当DO结构被CYCLE语句中断之后,如果DO结构存在DO变量的话,该DO变量就被更新,同时循环计数器被减小1,然后就开始了DO范围的下一个循环的执行。
【例11-19】
DO
…
INDEX=…
…
IF (INDEX<0)EXIT
IF (INDEX=0)CYCLE
…
END DO
在上面的例子当中,只要INDEX为正数,循环就一直进行;一旦INDEX为负值,则整个DO结构被终止;而如果INDEX为0时,则循环部分在CYCLE后面的所有语句都被跳过去,重新开始循环过程。
所谓分支就是使得程序的运行从当前的语句转移到该程序单位里的另外一个语句,也就意味着紧接当前语句的下面的语句得不到执行了。
导致转移的当前语句称为分支语句,而被转移到的得到执行的语句称为分支目标语句。
可以成为分支目标语句的语句包括所有的作用语句,另外还有如下语句:
● IF-THEN语句;
● SELECT CASE语句;
● DO语句;
● WHERE语句;
● FORALL语句;
● 以及其他在一定条件下的语句,包括:
● END SELECT语句,接受来自CASE结构里面的分支;
● END DO语句,接受来自DO结构里面的分支;
● END IF语句,接受来自IF结构里面的分支。
● END DO语句,终止DO作用语句以及终止DO共享语句,接受来自DO结构的分支,这种用法是过时的用法。
注意在一个块的内部的任意语句都不能接受来自块的外部的任意分支。
在转移到分支目标语句时,需要在分支语句当中给出目标语句的位置,因此就需要对目标语句标上标签。
当然从FORTRAN语法的角度来讲,任何语句都可以加上标签,但是现在一般在不必要的时候,都是省略掉标签的。因此一定要注意,分支语句引用了分支目标语句的标签,就意味着相应的分支目标语句必须具有相应的标签,绝对不能省略。
另外标签的形式既可以使用自由源码形式也可以使用固定源码形式。
GO TO语句是一种无条件的改变程序运行顺序的分支语句。
DO TO语句的句法形式(R836)为:
GO TO label
注意GO TO语句里的标签必须是引用在与GO TO语句同一个作用域单位里的分支目标语句,包括内部过程,派生类型的定义,以及界面块里面的语句的标签。
当执行GO TO语句时,接下来执行的是GO TO语句里面给出的标签所指定的分支目标语句,后面的程序运行则从该目标语句开始。
【例11-20】
GO TO 100 !表示无条件地转移到标签为100的语句
IF X=3,THEN A(X)=X**2 !尽管该语句紧接着上面的GO TO语句,
!但是由于它不具有标签100,因此不被执行。
GO TO 20
GO TO 020 !由于20和020是同一个标签,因此这两个
!GO TO语句含义一样。
CONTINUE语句的句法形式(R839)为:
[label] CONTINUE
CONTINUE语句主要用来作为DO终止语句,也可以作为分支目标语句。
如果CONTINUE语句只有它自己,没有充当任何功能,那么它对程序的运行顺序以及运行结果都没有任何影响。
【例11-21】
CONTINUE
20 CONTINUE
无论何时何地执行STOP语句,都能够终止程序的运行。
STOP语句的句法形式(R840)为:
STOP [scalar-character-constant]
STOP digit [digit [digit [digit [digit ] ] ] ]
其中的标量字符常量(scalar-character-constant)和数字(digit)列是可选的,称为停止码。
注意可选的字符常量必须是默认字符型。
可选的数码的第一个数字如果为0,是没有意义的。
这些停止码在程序终止后可以访问。如果存在多个STOP语句,这些停止码就可以用来标志程序终止的位置。对于停止码的解释依赖于编译系统所使用的局部中断过程。
【例11-22】
STOP
STOP 100
STOP “Error #909”
算术IF语句是一个基于算术表达式的三路分支语句。
它的句法形式(R838)为:
IF(scalar-numeric-expression)label,label,label
算术IF语句的一般规则如下:
● 在同一个算术IF语句当中,同一个标签可以重复出现不止一次。
● 其中的标量数值表达式(scalar-numeric-expression)不能是复型表达式。
● 语句当中的每个标签都必须是算术IF语句所在作用域单位里的作为分支目标的语句的标签。当然,具有这些标签的语句首先必须存在于此作用域单位之中。
算术IF语句的执行顺序是:首先计算数值表达式,如果为负值,则分支到第一个标签;如果得到0,则分支到第二个标签;如果得到正值,则分支到第三个标签。
算术IF语句现在已经很少用到,属于过时的语言成分。
类似于算术IF语句,计算GO TO语句通过计算一个整型表达式,根据得到的数值,从一系列的分支目标的标签当中选择下一个应该执行的分支目标语句。由于运用CASE语句能够实现完全相同的功能,而CASE语句更加具有结构化的特征,因此计算GO TO语句已经完全过时。
计算GO TO语句的句法形式(R837)为:
GO TO(label-list)[ ,]scalar-integer-expression
计算GO TO语句的一般规则如下:
● 如果在标签列表(label-list)当中出现了n个标签,而表达式的结果是从1到n的数值k,那么该数值表示了被选择的标签是列表当中的第k个标签,即分支到该标签所表示的语句继续程序的运行过程。
● 如果表达式的结果小于1或大于n,那么不进行任何分支转移过程,直接执行紧接该计算GO TO语句的下一个可执行语句。
● 标签列表当中的每一个标签,都必须是在与该计算GO TO语句同一个作用域单位里的分支目标语句的标签。
● 一个标签可以在标签列表当中出现不止一次。
【例11-23】
GO TO(15,45),A(X)
GO TO(33,47,32,1),X**2*I
【例11-24】
SWITCH=…
GO TO(33,100,33,56)SWITH
X=Y**3
X=Y+2
….
X=SIN(Y)
…
100 X=Y**(1/2)
如果SWITH取值为1或者3,则分支到语句33,执行语句33;如果SWITH取值为2,则分支到语句100;如果SWITH取值为4,则分支到语句56;如果SWITH取值大于4,小于1,那么在GO TO语句后,执行语句X=Y**3,然后再执行语句33。