【程序设计】3.3 [做循环] 条件循环
3.3 条件循环
for n 和 for a,b 都是确定次数的循环,但有时候我们不知道要循环多少次。要当某个条件成立(或者不成立)时才结束循环,之前一直重复执行循环体。这种循环称为条件循环。
事实上,for n 和 for a,b 也可以理解为这样的循环,即当循环变量超过 n 和 b(超过不一定是指大于)时就结束循环。条件循环是更一般的循环形式。
计算两个数的最大公约数,有个经典的方法叫辗转相除法。两个正整数 a 和 b,假设 a>b,则用 a 除以 b 的余数替代 a,现在 a<b 了,再用 b 除以 a 的余数替代 b,然后又有 a>b 了,…,如些反复,直到 a 和 b 中有一个是 0 了,则另一个还不是 0 的就是最初的 a 和 b 的最大公约数。因为 a,b 反复交换地位计算余数,所以叫辗转相除法。
这是二千多年前古希腊几何学家欧几里得发明的办法,有兴趣的读者可以自己证明,我们这里用程序代码来实现它。
设原始的两个数放在 A1 和 B1 格中。
A |
B |
|
1 |
7215 |
2345 |
2 |
for B1>0 |
=A1%B1 |
3 |
>A1=B1 |
|
4 |
>B1=B2 |
计算结果将会在 A1 中。
A2 格的代码 for B1>0,for 后面跟了一个计算结果是布尔值的计算表达式,当它计算出 true 时,循环体将被执行一次,然后回到 A2,再次计算这个布尔表达式,如果还是 true 则再执行循环体,…;直到某次计算这个布尔表达式为 false 时,循环中止。
这个逻辑就能实现欧几里得的辗转相除法。我们不知道要循环多少次,只知道当 B1 为 0 时循环就可以终止了。
大多数程序语言都有条件循环,但一般会使用 while 来表示。
类似 if,else,for 这样的单词,将在程序代码中用来表示某些特殊语句,称为程序语言的保留字或者关键字。这些保留字都有特别的意义,不能再被用作变量名等标识符。所有程序语言都会有一批自己的保留字。
SPL 不希望有太多保留字,而使用 for 来表示条件循环也没有歧义,就没有延用更常见的 while 保留字。
再来做一个和约数有关的问题,分解质因数,这是小学数学课学过的内容。
办法很简单,给定一个正整数 n,我们用 2 去除它,如果能整除,那么就得到它的一个因子 2。我们把 n 除以 2 得到一个新的 n,继续用 2 去除,…;直接不能被 2 整除为止,如果这时候 n 变成 1 了,那就结束了。如果 n 还不是 1,那么继续用 3 去除,重复这个过程;继续用 4,5,…;最后总会会把 n 变成 1,事情就结束了。
还是之前说的,因为还没学过集合,我们把找出来的质因数输出出来即可。
假定 n 先填入了 A1 格。
A |
B |
C |
|
1 |
7215 |
=2 |
|
2 |
for A1>1 |
for A1%B1==0 |
>output(B1) |
3 |
>A1=A1\B1 |
||
4 |
>B1+=1 |
注意在 C3 格要用整除 \,否则会算出浮点数,让下一步的 A1%B1 无法继续了。
这是段两层循环的代码,还有单层循环的写法:
A |
B |
C |
D |
|
1 |
7215 |
=2 |
||
2 |
for A1>1 |
if A1%B1==0 |
>output(B1) |
|
3 |
>A1=A1\B1 |
next |
||
4 |
>B1+=1 |
把 B2 格的 for 替换成了 if,然后增加了 D3 格的 next。
if 的作用我们已经知道了,这里的 next 是做什么用的?
next 的意思是进行下一轮循环头,不再执行循环体后面还没有执行过的代码。在这里,如果执行到 D3 的 next,就意味道着 B4 将被跳过不再执行,而直接进入下一轮循环,也就是再转回到 A2 来判断 A1>1 以决定继续执行循环体。
仔细思考这个逻辑过程,B1 开始是 2,如果 A1 能被 2 整数,那么在 C2 中输出这个 2 之后,并把 A1 除以 2,然后再次回到 A2 进入下一轮循环。这时候 B1 还是 2,因为 next 会跳过 B4,其中的语句 B1+=1 并没有被执行。要把所有的因子 2 都除掉之后,这时候会导致 B2 格的 if 不成立,才会进入 B4 格来执行 B1+=1,将 B1 变成 3,再进入下一轮循环。这个单层循环可以做到和上面两层循环同样的效果。
需要说明的是,如果把 next 用到 for n 或 for a,b 的循环中,在下一轮循环时,循环变量仍然会改变。比如前面那个求奇数和的例子,也可以用 next 来写:
A |
B |
C |
|
1 |
=0 |
||
2 |
for 200 |
if A2%2==0 |
next |
3 |
>A1+=A2 |
碰到偶数时,B2 的条件成立,就会执行到 C2 的 next,进入下一轮循环,循环变量仍然会加 1;而奇数时将会执行到 B3 来执行累加,循环体将被执行 200 次。
多层循环时,next 后加上循环格的名字,还可以直接让外层循环进入下一轮,比如前面讲过的水仙花数,也可以这么写:
A |
B |
C |
D |
E |
F |
|
1 |
for 9 |
=100*A1 |
=A1*A1*A1 |
|||
2 |
for 0,9 |
=B1+10*B2 |
=C1+B2*B2*B2 |
if C2<D2 |
next A1 |
|
3 |
for 0,9 |
=C2+C3 |
=D2+C3*C3*C3 |
|||
4 |
if D3==E3 |
>output(D3) |
||||
5 |
else if D3<E3 |
next B2 |
看起来使用 next 达到了和 break 同样的效果,但其机理却不一样。E5 格的 next B2 会直接进入 B2 循环的下一轮,而如果是 break 则是跳出 C3 循环。在这个例子中,两者效果相同,是因为在 C3 循环结束后也不再有 B2 循环的代码,B2 循环也结束了。
如果代码改成这样:
A |
B |
C |
D |
E |
F |
|
1 |
for 9 |
=100*A1 |
=A1*A1*A1 |
|||
2 |
for 0,9 |
=B1+10*B2 |
=C1+B2*B2*B2 |
if C2<D2 |
next A1 |
|
3 |
for 0,9 |
=C2+C3 |
=D2+C3*C3*C3 |
|||
4 |
if D3==E3 |
>output(D3) |
||||
5 |
else if D3<E3 |
next B2 |
||||
6 |
>output("B2") |
E5 代码是 next B2 时,C6 格不会被执行到;如果是 break,则 C6 格还将被执行。读者可以自己尝试一下体会这之间的差别。
这个 next 就是 continue 的加强版,带点 goto 的意思,最后一个表中的 next B2 直接换 goto B2 会怎么样?
next 是把当前循环序号加 1 然后执行下一次循环
goto 如果跳出了循环的代码块(包含 for 所在的格子)则会结束循环
这个例子如果改成 goto B2 则会结束 B2 循环,然后把执行光标的位置设成 B2,再从头重新执行 B2 格的 for