第三章: 循环结构
单纯循环
3.1.1
GOTO语句实现循环
循环结构用于实现重复的算法。它是三种基本结构(顺序、选择、循环)之一,具有重复执行某一段语句的功能,因为它以DO作为关键字,所以又称DO构造。在程序中存在两类循环:无条件的循环和有条件的循环。无条件循环是无休止地执行一个程序段,而有条件的循环是在满足一定条件时才执行循环。
F77允许使用GOTO语句来实现转移。GOTO语句的一般形式为:GOTO (语句标号)。由于GOTO语句破坏了语句顺序执行的正常状况,不符合结构化原则,因此一般不提倡使用GOTO语句。只有在一个基本结构内部可以使用GOTO语句。利用GOTO语句可以实现循环处理。如果在逻辑IF语句中使用GOTO语句就可以实现有条件的循环,循环操作能够在一定条件下结束。
3.1.2有循环变量的DO构造
DO构造可分为不带循环变量与带循环变量两种形式。
当需要执行的循环次数为已知时,用DO语句实现循环比较方便。它的一般形式为:
DO [[标号][,]] 循环变量=初值式,终值式[,增量式]
例:循环读入学生的学号和成绩30次并打印。
DO 10, N=1,30,1
READ *,NUM,GRADE
10
PRINT *,NUM,GRADE
上面是一个循环,第一行DO语句称为循环语句,DO后面的数10是一个标号,表示循环的范围到标号为10的语句为止,也就是反复执行READ语句和PRINT语句。DO语句中的N是“循环变量”,用它来控制循环次数,“N=1,30,1”的意思是:N的初值为1,终值为30,每执行一次循环N的值增加1。当N再变化到31时,由于它已超过了指定的终值30,不再执行循环。
下面的DO语句是合法的:
DO 10,I=1,10,2
DO 20 N=1,5
DO 100,X=1.2,2.4,0.2
DO T=2.5*2,5O./3.,0.3
DO M=1.5,12.5,15
DO语句的一些特点是:
在上述DO语句的一般形式中,当循环变量的增量(步长值)为1时增量式可不写。
循环变量初值、终值和步长可以分别是常数、变量或表达式。如果是变量则它应预先被赋值。如果是表达式,则先计算出表达式的值。循环次数可以从循环初值、终值和步长计算出来:次数=INT((终值-初值+增量)/增量)。如果计算出的循环次数<0时,则按0处理,即一次也不执行循环。
例:对于D0 I=1,10,2 其循环次数=INT((10-1+2)/2)=5次。I按序分别取值为:1,3,5,7,9。对于D0 I=10,1,2 则循环次数=0次。I不可取值,程序运行到这里时将跳过此循环。
循环变量的初值、终值和步长可以为正或负。初值、终值可以为零。但步长不应为0,否则循环变量的值永远不会超过终值,从而陷入死循环。
例:对于D0 I=-1,-3,-1 其循环次数=INT((-3+1-1)/(-1))=3次。I按序分别取值为:-1,-2,-3。
如果循环变量的类型和初值、终值和步长的类型不一致,则按赋值的规则处理,即需先将初值、终值和步长的类型转化成循环变量的类型,然后进行处理。为避免错误,应尽量使循环变量类型与初值、终值和步长的类型一致。
例:对于D0 I=1.5,3.6,1.2 不要根据INT((3.6-1.5+1.2)/1.2)=2而认为循环次数为2,而应当先将实型量转化为整型量,即变成相当的循环语句 DO I=1,3,1 其循环次数为3次而不是2次。
例:对于D0 X=1.5,3.6,1.2 由于循环变量不是整型的而是实型的,它的循环次数为2次。X取值分别是1.5,2.7。
由于实型数在运算和存储时有一些误差,因而循环次数的理论值与实际值之间会有一些差别。这种情况在程序设计中常有发生,而且比较隐蔽不易发现。所以应该避免使用实型的循环变量,用整型循环变量计算出的循环次数是绝对准确的。
例:对于D0 X=0.0,50.0,0.1理论循环次数=INT(50.1/0.1)=501,但实际上在许多计算机上它只执行500次循环。原因是实数在内存中的误差使得增量值不是准确的0.1,由于循环的误差积累,到执行完500次循环后X的值可能已超过50.O,因而停止执行循环。改用整型循环变量时,则循环改写为:D0 I=0,500; X=I/10。
循环执行过程按序为以下几个步骤:
(1).计算初值式、终值式、增量式的值,并将它们转换成循环变量的类型。
(2).将初值赋予循环变量。
(3).计算应循环的次数。
(4).检查循环次数,若≤0则跳过循环体,执行循环终端语句下面的一个执行语句。如果>0,则执行循环体。
(5).执行终端语句时,循环变量增值。
(6).循环次数减1。
(7).返回(4),重复执行(4)、(5)、(6)、(7)。
上面介绍的循环中,循环终端语句为一般的执行语句。F90规定:循环终端语句可以是除了GOTO、块IF、CASE、CYCLE、DO、ELSE、ELSE IF、END IF、END、END SELECT、EXIT、SELECT CASE、STOP和RETURN语句以外的任一可执行语句,如打印语句、赋值语句、输入语句、逻辑IF语句等都可以作为终端语句。特殊的循环终端语句是:END DO(常用于无语句标号时)和CONTINUE(常用于有语句标号时)。END DO语句使老的CONTINUE语句显得没有什么用处了,虽然F90的向下兼容性使CONTINUE语句仍然可用,但新编写的程序应该尽量使用以END DO结束的块DO构造。
例:用展开式求指数函数的数值。[e_312_01.f90]
CONTINUE语句本身不进行任何机器操作,只是将流程转到逻辑上的下一个语句,因此,CONTINUE语句又称为“空语句”,即进行“空操作”。与CONTINUE语句(继续功能)相对应的停止功能语句是STOP和PAUSE语句。
PAUSE语句(在F90中不推荐使用,在F95中被废除)暂时中止程序的运行,将系统挂起,使程序操作员可以执行其它操作系统命令。它的一般形式是:PAUSE [暂停值],暂停值为字符串常量或5位数以下的整型数,当程序运行至断点处将输出暂停值。如无暂停值的话,系统将输出默认的信息,WinNT/9x系统上输出“回车才能继续”的信息。
例:PAUSE 701
PAUSE
'ERROR DETECTED'
在这些例子中,它的用处是在程序中加入断点把程序分段,以便于一段一段地调试程序。Visual Fortran的Debug功能可代替这种程序调试方法,但它也可以用于一些输出情形。[e_312_02.f90]
STOP语句是停止运行,一个程序单位中可以有多个STOP语句,执行到任一个STOP语句处时,程序即完全中止运行。在子程序中如果有STOP语句,也是使整个程序停止运行而不是使控制返回主程序。STOP语句的一般形式为: STOP [停止值],与PAUSE语句类似,程序停止运行时将输出停止值。在F66中,END不作为执行语句而只作为程序单位的结束标志,需要在END之前用STOP语句使程序结束运行。有些人在写F77程序时仍保留此习惯,在END语句之前又写了一个STOP语句。
在一个DO循环中又完整地包含另一个DO循环,称为DO循环的嵌套。嵌套层数可以不限,各层的循环变量不允许同名。注意内循环应当完整地嵌套在外循环之内,即内循环是外循环体中的一部分,内外循环不能交叉。即:
do
i=1,1O
do j=1,20
………
end do
end
do
程序的执行过程是外循环执行一次,内循环执行一遍。[e_312_03.f90]
例:有10个实数,将它们按大小排列。[e_312_04.f90]
循环变量可以在循环体中被引用,但不应当再被赋值。
例:下面写法是不正确的,循环变量N不能在循环体内被重新赋值。
do
n=1,1O
………
n=2*n
end
do
循环的次数是根据循环变量的初值、终值和步长值计算出来的,在执行循环体期间是确定不变的。
可以用转移语句从循环体内转到循环体外,也可以在循环体内转移,但不允许从循环外转到循环内。(块规则)
例:下面写法是合法的(尽管不符合结构化原则)。
do
n=1,1O
………
if(n*x.gt.1.) go to 10
end
do
10 ………
例:下面写法是非法的。
if(x.gt.1.) go to 10
do
n=1,1O
………
10 ………
end
do
多个DO循环可以共享一条循环终端语句,但循环体必须完全包含在外围DO循环体内。
如果DO循环出现在if,else if或else块内,则DO循环范围必须完全包含在该块中。
如果IF语句和SELECT CASE语句出现在DO循环范围内,则相应的END IF语句和END SELECT语句也必须出现在这个DO循环体内。
隐DO循环实际上是带控制循环变量的DO循环,但简化成只有DO循环的第一句,且把关键字DO隐去,成为I=m1,m2,m3形式。它不是独立语句,只是用作为读写语句的输入输出表中一个组成部分,用来控制重复读写的次数。例如:
READ *,(VALUE(I),I=1,20)
表示读入VALUE(1),VALUE(2),…,VALUE(20)的值。
WRITE(*,*)(A,B,N=1,5)
表示在当前设备用默认格式重复输出A、B的值5次。
隐DO循环只能作为输入输出表的一部分出现,不能用于其它场合。此时输入输出表的一般形式为:(I/O列表,循环变量名=初值,终值,增值)。也即把输入输出表与循环控制部分一起用括号括起,中间用逗号分开,称为隐DO表,写在读写语句后面作为读写对象。
隐DO表可以嵌套,如:
PRINT *, ((A(I,J),I=1,3),J=1,3)
先把内层(A(I,J),I=1,3)隐DO表作为输出表,再与隐DO控制J=1,3合成外层隐DO表。其控制机制与嵌套DO循环一致,先内层循环完,外层循环变量加一步长,再循环完内层。其打印值的次序如下:
A(1,1),A(2,1),A(3,1),A(1,2),A(2,2),…,A(3,3)
即先输出第1列,再输出第2列、第3列。如果把J作为内层,I作为外层循环变量,则输出是按行的:
PRINT *,((A(I,J),J=1,3),I=1,3)
则打印输出值的次序为:
A(1,1),A(1,2),A(1,3),A(2,1),A(2,2),…,A(3,3)