局部变量

简介

在前面的章节中,我已经讲述了如何定义函数。在本节中,我讲介绍局部变量,这将会使定义函数变得更加容易。

let表达式

使用let表达式可以定义局部变量。格式如下:

  1. (let binds body)

变量在binds定义的形式中被声明并初始化。body由任意多个S-表达式构成。binds的格式如下:

  1. [binds] ((p1 v1) (p2 v2) ...)

声明了变量p1p2,并分别为它们赋初值v1v2。变量的作用域(Scope)body体,也就是说变量只在body中有效。

例1:声明局部变量ij,将它们与12绑定,然后求二者的和。

  1. (let ((i 1) (j 2))
  2. (+ i j))
  3. ;Value: 3

let表达式可以嵌套使用。

例2:声明局部变量ij,并将分别将它们与1i+2绑定,然后求它们的乘积。

  1. (let ((i 1))
  2. (let ((j (+ i 2)))
  3. (* i j)))
  4. ;Value: 3

由于变量的作用域仅在body中,下列代码会产生错误,因为在变量j的作用域中没有变量i的定义。

  1. (let ((i 1) (j (+ i 2)))
  2. (* i j))
  3. ;Error

let*表达式可以用于引用定义在同一个绑定中的变量。实际上,let*只是嵌套的let表达式的语法糖而已。

  1. (let* ((i 1) (j (+ i 2)))
  2. (* i j))
  3. ;Value: 3

例3:函数quadric-equation用于计算二次方程。它需要三个代表系数的参数:abcax^2 + bx + c = 0),返回一个存放答案的实数表。通过逐步地使用let表达式,可以避免不必要的计算。

  1. ;;;The scopes of variables d,e, and f are the regions with the same background colors.
  2. (define (quadric-equation a b c)
  3. (if (zero? a)
  4. 'error ; 1
  5. (let ((d (- (* b b) (* 4 a c)))) ; 2
  6. (if (negative? d)
  7. '() ; 3
  8. (let ((e (/ b a -2))) ; 4
  9. (if (zero? d)
  10. (list e)
  11. (let ((f (/ (sqrt d) a 2))) ; 5
  12. (list (+ e f) (- e f)))))))))
  13. (quadric-equation 3 5 2) ; solution of 3x^2+5x+2=0
  14. ;Value 12: (-2/3 -1)

这个函数的行为如下:

  1. 如果二次项系数a0,函数返回'error
  2. 如果a ≠ 0,则将变量d与判别式(b^2 - 4ac)的值绑定。
  3. 如果d为负数,则返回'()
  4. 如果d不为负数,则将变量e-b/2a绑定。
  5. 如果d0,则返回一个包含e的表。
  6. 如果d是正数,则将变量f√(d/2a)绑定,并返回由(+ e f)(- e f)> 构成的表。

实际上,let表达式只是lambda表达式的一个语法糖:

  1. (let ((p1 v1) (p2 v2) ...) exp1 exp2 ...)
  2. ;⇒
  3. ((lambda (p1 p2 ...)
  4. exp1 exp2 ...) v1 v2)

这是因为lambda表达式用于定义函数,它为变量建立了一个作用域。

练习1

编写一个解决第四章练习1的函数,该函数旨在通过一个初始速度v和与水平面所成夹角a来计算飞行距离。

总结

本节中,我介绍了let表达式,let表达式是lambda表达式的一个语法糖。变量的作用域通过使用let表达式或lambda表达式来确定。在Scheme中,这个有效域由源代码的编写决定,这叫做词法闭包(lexical closure)

习题解答

答案1

  1. (define (throw v a)
  2. (let ((r (/ (* 4 a (atan 1.0)) 180)))
  3. (/ (* 2 v v (cos r) (sin r)) 9.8)))