概述
今天讲课,将顺序、分支和循环这三种流程结构聊了聊。本文作为泛读资料(有问题请跟帖提问)。
在狄杰斯特拉(Edsger W. Dijkstra)反复研究面条式代码(spaghetti code),并在1968年给某位编辑写了一篇著名的简信,题为《Go to语句是有害的》之后,计算机科学家Corrado Bohm和Giuseppe Jacopini证明,使用顺序(sequencing),分支/选择(alternation)和循环(iteration)这三种流程结构就足以表达所有程序的本质。三种流程结构如同万花筒中的彩纸,构成无穷无尽极富变化的程序。早期的编程书籍中,会有顺序程序设计,选择结构程序设计和循环结构程序设计等说法。有时候,人们将它们称为控制抽象(control flow abstraction)。随着变量、表达式和控制语句被所有的现代语言支持,人们已经习以为常地不将它们作为抽象或抽象机制了,仅将它们作为语句或程序的实现看待。
三种流程结构的进一步说明,顺序结构参见[0.2.2命令编程范式],分支结构参见[1.2.5策略模式(5.9)],循环结构参见[0.2.3函数编程范式]。下面综合的说明一下。
1.顺序结构(命令范式Vs.函数范式)
一江春水向东流。在命令编程范式如C语言中,将编程过程定义为一个命令/指令序列的开发,这些指令操作数据而改变机器的状态,程序则由一系列的语句组成。因此,其程序一般有这样的形式[1]:
statement1;
statement2;
……
但是,函数编程范式中,一个个函数解决小问题,组合起来解决大问题。因此Scheme仅对一些有副作用的表达式(命令),Scheme以begin块封装一系列的表达式,顺序执行。
(begin (display "Hello") (newline) )
其中:
- display命令,输出它的参数即字符串。
- newline命令,输出一个换行。
2.分支结构(OO中)
虽然C与Scheme有些差异,总之都是分情况进行不同处理(参考循环中的例子)。但是,如果有人在OO中不假思索地选择分支结构,通常是垃圾程序员。多态本身就是根据具体对象的不同执行不同的行为,是天然的分情况进行不同处理。在面向对象程序中,通常程序员会自然地选择多态,分支结构通常较少使用。面向对象程序员在遇到少量需求变化时,可能会通过分支结构简单应付;如果需求变化较多时,通常会以多态重构其分支结构。
3.循环(迭代Vs.递归)
【我把这部分内容放在 0.2.3函数编程范式 中】C/Java语言教学中,会讲循环/迭代语句;而Scheme会介绍递归。Java也可以递归,如下:
static long sum1(long n) {
return (n == 1)? 1:(n + sum1(n - 1));
}
为什么纯函数式编程语言如Scheme要用递归而且能够用递归呢?
首先,纯函数式编程语言不喜欢变量的值不断地变更,数据的不变性是他们的宗教信仰。例如for(int i=0;i < n;i++)中,i总在变,转一圈++一下。对于Scheme而言,这是违反其基本价值观的异端,因此Scheme中不采用迭代结构如while、for。
其次,递归,更一般地,函数的调用,都有创建新的栈帧/stack frame的开销。Java程序的递归,由于JVM为一个Java程序预备的内存有限,大量地递归调用会出现栈溢出错误。如果函数对另外一个函数的调用是尾调用,解释器可以通过尾调用优化/tail-call optimization避免帧的创建,所以Scheme中,如果递归函数是尾递归,事实上是迭代执行的(SICP1.2.1中比较含混地说明了这一点)。命令式语言/C语言程序员最喜欢赋值语句,所以程序员使用循环/迭代语句非常自然和爽快,以至于C、Java编译器迟迟不愿意添加尾调用优化——没有动力。而使用Scheme语言编写尾递归,就能够等价于Java的while、for等迭代结构。
当然,如果Scheme使用普通的递归(SICP中称其为“线性递归过程”),也会和Java递归一样,导致栈溢出错误。
Scheme需要通过尾递归达到Java使用for语句的效果(SICP中称其为“迭代计算过程”)。简单的尾递归和使用状态变量的尾递归均可。
(define (sum n)
(if (= n 1)
1
(+ n (sum (- n 1)))))
;;;(sum 5000000) ;;;栈溢出
;;;尾递归
(define (sum-iter acc n)
(if (= n 1)
acc
(sum-iter (+ acc n) (- n 1))))
(define (sum n) (sum-iter 1 n))
(sum 5000000)
;;;使用状态变量
(define (sum-iter total i max)
(if (> i max)
total
(sum-iter (+ total i)
(+ i 1)
max)))
(define (sum n) (sum-iter 0 1 n))
(sum 5000000)
总之,循环语句是采用迭代还是递归,源于一门编程语言的基本价值观(和尾调用优化支持)。顺便提醒:关于循环,对它的理解分三个层面:①基本实现:迭代 vs递归,②迭代器,③流。
[1]这些特点在Java的方法实现中保留,因此Java是命令型的面向对象语言。
最后
以上就是无心水蜜桃为你收集整理的漫谈顺序、分支和循环的全部内容,希望文章能够帮你解决漫谈顺序、分支和循环所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复