defvar、defparameter、setf和setq之间有什么区别?

52

我找到了一个类似的问题

但是我不太理解那个解释。

所以我正在尝试使用以下示例运行clisp:

  [1]> (defvar a 5)
  A
  [2]> (+ a 1)
  6
  [3]> (defparameter b 5)
  B
  [4]> (+ b 1)
  6
  [5]> (setf c 5)
  5
  [6]> (+ c 1)
  6
  [7]> (setq d 5)
  5
  [8]> (+ d 1)
  6
  [9]> (let ((a 500)) (+ a 1))
  501
  [10]> (let ((b 500)) (+ b 1))
  501
  [11]> (let ((c 500)) (+ c 1))
  501
  [12]> (let ((d 500)) (+ d 1))
  501
  [13]> 

我发现的完全一样。

但我搞不清楚它们有什么不同?

2个回答

79

DEFPARAMETER 总是会分配一个值。因此:

[1]> (defparameter a 1)
A
[2]> (defparameter a 2)
A
[3]> a
2

DEFVAR只执行一次,因此:

[4]> (defvar b 1)
B
[5]> (defvar b 2)
B
[6]> b
1

SETF是一个宏,它在内部使用SETQ,但具有更多的可能性。 从某种意义上说,它是一个更通用的赋值运算符。例如,使用SETF,您可以执行以下操作:

[19]> (defparameter c (list 1 2 3))
[21]> (setf (car c) 42)                                              
42
[22]> c
(42 2 3)

但是你不能使用SETQ实现这一点:

[23]> (setq (car c) 42)                                              
*** - SETQ: (CAR C) is not a symbol
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead.
ABORT          :R2      Abort main loop
Break 1 [24]> abort

如果我用defvar定义一个变量,我可以通过defparameter改变它的值吗?这是正确的做法吗?还是只有在defparameter中定义的变量才能被defparameter改变?谢谢~ - sam
38
在文件中进行值初始化的正确方法是使用DEFVAR和DEFPARAMETER,使用其中之一在listener中声明动态变量,并始终使用SETF在非顶层代码中更改值。然后,DEFVAR和DEFPARAMETER之间的区别在于,“我是否希望每次加载此文件时重置该值”(使用defparameter),还是“不需要?”(使用defvar)。 - Vatine

31

defvardefparameter都会声明一个“动态作用域变量”。此外,defparameter将始终将变量的值设置为您作为第二个参数传递的值。这与defvar不同,后者仅在变量之前未设置值时才会设置变量的值。

在全局词法作用域中使用setfsetq定义变量是未定义的。一些实现会为您创建一个动态作用域变量,而另一些则不会。当您第一次这样做时,您可能会看到诊断消息。

要理解词法作用域变量和动态作用域变量之间的区别,请尝试以下代码片段:

* (defvar *a* 1)

*A*
* (let ((*a* 5)) (defun demo-a () *a*))

DEMO-A
* (let ((b 5)) (defun demo-b () b))

DEMO-B
* (let ((*a* 100)) (demo-a))

100
* (let ((b 100)) (demo-b))

5

在这里,我们创建一个动态作用域变量和一个返回该值的函数(在一个绑定内定义,其中它在函数创建期间具有不同的值,这不是必要的,只是为了看起来类似于对b的词法闭包)。然后我们定义一个新变量并定义一个函数返回其值。

之后,我们调用两个函数,在闭包内将一个值绑定到同名变量。在动态作用域情况下,它是同一变量。在词法闭包情况下(b),它们只是具有相同的名称,但不是同一变量,因为它们在两个不同的词法闭包中定义。

至于setfsetq之间的区别,请尽可能使用setf(我想不出任何例子,其中(setq blah blahblah)可以起作用,而(setf blah blahblah)不能完成同样的工作)。


现在我知道 defvar 和 defparameter 的区别了。但是什么情况下应该使用 setf 或 setq 呢?谢谢~ - sam
8
请使用 defvardefparameterlet 来引入新变量,使用 setfsetq 来更改已有变量的值。将它们用于引入新变量是未定义的行为。 - Rörd
最后一个例子 (let ((b 100)) (demo-b)) 给我返回的结果也是100,而不是显示的5。 - Andreas Röhler
1
如果是这样的话,您可能之前已经执行了一个顶层的(setq b...),这将导致多个Lisp环境将该变量转换为动态作用域。如果您在新启动的Common Lisp中尝试它会发生什么? - Vatine
@Vatine 啊,抱歉,我在说Emacs Lisp是动态作用域。 - Andreas Röhler
值得一提的是,(demo-a) 如果没有 let 包围其调用,则会计算为 1 - gypsydave5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接