PL真有意思(四):控制流 (4)

迭代和递归是计算机能够重复执行一些操作的两种机制;命令式语言倾向于使用迭代、函数式语言则更看重递归。大多数语言中的迭代都是以循环的形式出现的,和复合语句中的语句一样,迭代的执行通常也是为了副作用,也就是修改一些变量的值。根据用何种方式控制迭代的次数来看,循环有两个主要变种"枚举控制的循环"和“逻辑控制的循环”。前者是在给定的某个有限的集合中执行,后者则是不确定要执行多少次(直到它所依赖的表达式结果被改变)。对于这两种结构,大多数的语言都提供了不同的语法结构来表示。

枚举控制的循环

枚举控制的循环最初来自Fortran的do循环,

do i = 1, 10, 2 ... enddo

等号后面的表达式分别是i的初始值,边界值和步长

像这种枚举循环可以说的不多,但是如果前几次迭代的执行会导致迭代的次数或下标值的发生变化,那么我们就需要一个更通用的实现

思考几个问题:

控制是否可以通过枚举之外的任何方式进入和离开循环呢?

如果循环体修改了用于计算循环结束边界的变量,会发生什么?

如果循环体修改了下标变量,会发生?

程序是否可以在循环结束后读取下标变量,如果可以,它的值将是什么?

现在的大多数语言都提供了,break类似的机制来离开循环。Fortran IV允许通过goto跳入到一个循环中,但是这个通常被认为是一个语言缺陷

同样的,在大多数语言中,边界值只在第一次计算,并且保存在一个临时寄存器中,所以对于之后的修改并不会起作用

早期的大部分语言都禁止在枚举控制的循环中修改下边变量。但是刚刚试验了一下,许多的语言好像都放开了这个禁止,也就是按照修改后的正常逻辑继续执行

首先这是一个语言实现的问题,现在的大多数语言应该都是将循环下标的作用域限定在循环体内了,所以出了循环体是访问不到的

当然在之后出现了C的for循环

for (int i = first; i < last; i += step) { ... }

这样有关结束条件、溢出和循环方向的问题全都交由程序员来掌控

迭代器

上面描述的循环都是在算术值的序列上迭代。不过一般而言,我们还希望可以在任何定义的良好的集合的元素上迭代。在C++和Java里叫做迭代器

真迭代器

Clu,Ruby等语言允许任何容器对象提供一个枚举自己元素的迭代器,这种迭代器就像是允许包含yield语句的子程序,每次yield生成一个循环下标

在Python里就可以这样写

for i in range(first, last, step): ...

在被调用时,这个迭代器算出循环的第一个下标值,然后通过yield语句返回给调用者,yield语句很像return,但是不同的是再每次循环结束后控制权会再次的交给迭代器,重新开始下一次yield,直到迭代器没有元素可yield为止才结束for循环。从效果上看迭代器就像是另外一个控制线程,有它自己的程序计数器,它的执行与为它提供下标值的for循环交替执行,这一类通常称为真迭代器。

迭代器

在许多面向对象语言里,用了更加面向对象的方法来实现迭代器。它们的迭代器就是一个常规对象,它提供了一套方法,用来初始化、生成下一个下标值和检测结束条件

BinTree<Integer> myTree; for (Integer i : myTreee) { }

上面的这段代码其实是下面这段的一个语法糖

for(Iterator<Integer> it = myTree.iterator();it.hasNext();) { } 用一级函数来实现迭代器

实现是将循环的体写成一个函数,用循环的下标作为函数的参数,然后将这函数作为参数传递给一个迭代器

(define uptoby (lambda (low high step f) (if (<= low higt) (begin (f low) (uptoby (+ low step) high step f)) '()))) 不用迭代器的迭代

在那些没有真迭代器或者迭代器对象的语言中,还是可以通过编程方式实现集合枚举和使用元素之间的解耦的,用C语言做例子:

tree_node *my_tree; tree_iter ti: ... for(ti_create(my_tree,&ti); !ti_done(ti); ti_next(&ti)){ tree_node *n=ti_val(ti); ... } ti_delete(&ti); 逻辑控制的循环

和枚举循环相比,逻辑控制的循环关注点只在结束条件上

前置检测

由Algol W引进,后来被Pascal保留

while cond do stat

后置检测

这种的循环体不管是否满足循环条件,都至少会执行一次循环体。如C语言的do while语句

do{ line=read_line(); //...代码 } while line[0]!='$';

中置检测

中置检测一般依赖if

for(;;){ line=read_line(); if line[0]!='$' break; } 递归

递归和上述讨论的其他控制流都不同,它不依赖特殊的语法形式,只要语言允许函数直接或间接的调用自身,那么就是支持递归的。大部分情况下递归和迭代都可以互相用对方重写的。

迭代和递归

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfgwf.html