为了与其他语言(如 C、C++、Java 或 Python)进行比较,您的代码更改的是“局部变量”,即使这不是 Lisper 会使用的措辞(Lisp 用语中的措辞将是本地“绑定” )。
您可以像示例一样使用函数参数或使用一些标准形式来创建局部变量,例如:
(let ((x 12)) ...)
(do ((x 0 (1+ i))) ...)
(dotimes (x 10) ...)
(loop for x from 0 to 10 do ...)
另一方面,在您的实现中,可能所有局部变量都是使用参数创建的,而其他形式只是扩展为该参数的宏。例如:
(let ((x 10)) ...)
相当于
(funcall (lambda (x) ...) 10)
另请注意,确实阅读您的代码片段x
在某种意义上可能是“全局变量”,因为它可能已被声明为特殊:
(defvar x 12)
;; ==> x
(defun bar ()
(format t "The value of x is ~a" x))
;; ==> bar
(defun foo (x)
(bar))
;; ==> foo
(foo 42)
The value of x is 42
;; ==> NIL
x
;; ==> 12
如果您使用它声明一个变量“特殊” (defvar ...)
,则处理方式将有所不同:就像每次将其用作参数或以某种(let ..)
形式使用它时,代码将使用新提供的值保存当前值,然后退出函数后恢复值或let
.
因此,这些变量既是“全局的”(因为外部函数可以看到它们),也是局部的(因为在您的函数或 let 终止后,先前的值将被恢复)。
标准约定是用“earmuffs”命名特殊变量,即在名称的开头和结尾都用星号,如:
(defvar *x* 12)
这有助于阅读您的代码的人了解该变量是特殊的。但是请注意,这不是语言强制要求的,任何名称都可以用于特殊变量。
没有什么类似于 C、C++、Java 或 Python 中的特殊变量。
最后一点关于setq
和setf
。这里的事情有点棘手,因为您需要了解较低级别的 Lisp 才能了解为什么setq
需要。如果您使用的是 Common Lisp,那么您应该忘记setq
并始终使用setf
.
setf
是一个宏,将setq
在需要时扩展(但也setq
可以setf
在需要时更改(符号宏),这可能会让新手感到困惑)。
您的最后一个示例是“关闭”的情况。当您定义一个函数(使用(lambda ...)
表单命名或未命名)时,该函数可以“捕获”可见的变量并在以后使用它们。一个更简单的例子是“加法器”:
(defun adder (x)
(lambda (y) (incf x y)))
此函数返回一个函数,该函数将继续将传递的值添加到内部累加器:
(let ((f (adder 10)))
(print (funcall f 3))
(print (funcall f 9))
(print (funcall f 11)))
输出将是 13 (10 + 3)、22 (13 + 9) 和 33 (22 + 11)。
匿名函数“捕获”了局部变量x
,即使在退出adder
函数后也可以使用它。在 C、C++ 或 Java 等语言中,当您退出定义变量的范围时,局部变量将无法生存。
C++11 有未命名的函数,但仍然无法捕获变量并在作用域内存活(可以将它们复制到未命名函数的局部变量中,但这不是一回事)。