如果我们定义一个函数,如下所示
(defun foo(x)
(setf x somevalue))
x
定义为局部变量还是全局变量?使用 setf/q 将值设置为全局变量。如果它是全局的,谁能告诉我如何在 Lisp 中定义除 let
之外的局部变量?
谢谢!
考虑以下示例
(let ((x 0))
(defun foo (y)
(when (equal x 0) (setq x y))
(when (< x y) (setq x y))
x))
当我给像(foo 2)
这样的foo
一些输入时,它返回 2,如果我们再次使用 (foo 1)
执行该函数,它仍然返回 2,(foo 3)
返回 3。但问题是这怎么可能,因为如果我尝试从 clisp 终端访问函数外部的变量x
,我无法这样做。如果我再次访问该函数,它似乎保留了以前的 x
值。
谢谢!
为了与其他语言(如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 ...)
声明一个变量"special",它将以不同的方式处理:就像每次将其用作参数或以(let ..)
形式使用它时,代码将要做的是保存当前值,使用新提供的值,然后在退出函数或let
后恢复值。
所以这些变量既是"全局的"(因为外部函数可以看到它们(,也是局部的(因为在你的函数或 let 终止之后,以前的值将被恢复(。
标准惯例是用"耳罩"命名特殊变量,即在名称的开头和结尾都带有星号,例如:
(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 具有未命名的函数,但仍然无法捕获变量并在作用域中幸存下来(它们可以复制到未命名函数的局部变量中,但这不是一回事(。
实际上,x
是函数的局部,因此setf
或setq
它只会更改局部变量x
。
为了回答问题的第二部分,除了 let 之外,还有另一种方法可以定义局部变量:
(funcall (lambda (var1 var2...)
... use var1 var2 ...)-
val1 val2 ...)
事实上,defun
可以实现为
`(setf (symbol-function ,name) (lambda ,args ,@body))
尽管我检查过的所有实现都不止于此。
此外,symbol-function
位置允许您执行此操作:
(setf (symbol-function '+) (function -))
虽然这通常是一个坏主意。
你的第二个问题
那里发生的事情是x
是包含defun
的范围的本地。它不是全局变量。你正在用这个defun
做的是创建一个闭包,它"捕获"周围作用域中的所有词法范围(不是使用 defvar
/defparameter
创建的(变量,并保留它们以备将来使用。如果要检查 x
的值,请添加另一个函数
(defun get-x () x)
let
里面.换句话说,x
是函数所在let
的本地。您提供的情况类似于:
(let ((x 3))
(print x)
(let ((y 7))
(print (list x y))
(setf x y))
(print x))
除了内部let
被defun
(这是一个lambda
(取代。
在代码中,x
是 f
函数的参数,因此是函数的本地参数。对 setf
的调用不会创建新变量,而只是将现有局部变量的值设置为 x
。使用 setq
而不是 setf
的行为相同。