Common Lisp:按值传递与按引用传递[重复]
Posted
技术标签:
【中文标题】Common Lisp:按值传递与按引用传递[重复]【英文标题】:Common Lisp: pass by value vs pass by reference [duplicate] 【发布时间】:2021-12-16 03:58:29 【问题描述】:我想用 Common Lisp 编写一个函数,它会破坏性地修改它的参数。在 C 或 Rust 中,我会使用一个指向对象的指针/引用,它可以在函数体内被取消引用。我在 CL 中写道:
(defun foo (lst)
(setf lst NIL))
但是在评估这个表格之后:
(let ((obj (list "a" "b")))
(foo obj)
obj) => ("a" "b")
看到函数foo
没有效果。我可以通过按值语义来解释这一点,我们修改推入函数堆栈的参数的本地副本。
如果我们定义另一个函数:
(defun bar (lst)
(setf (car lst) NIL))
并评估类似的形式
(let ((obj (list "a" "b")))
(bar obj)
obj) => (NIL "b")
我们将清楚地看到lst
被修改,就好像我们将使用按引用传递语义一样。所以,(setf lst NIL)
没用,但 (setf (car lst) NIL)
没用。你能解释一下原因吗?
【问题讨论】:
问题出在setf
。第一个参数需要place
。在foo
中,符号lst
脱离了上下文,但在bar
中,该位置仍然存在于外部上下文中。
setf
用符号作为位置表示您想要改变变量指向的内容,而不是它绑定的对象。例如。 lst = 0
与 (setf lst nil)
相同。 lst
是一个对象,所以(setf (car lst) ...)
改变了对象的汽车访问器,因此你现在更新的是对象而不是绑定。
【参考方案1】:
Common Lisp 按值传递参数,但在大多数情况下(除了原始值类型,如 fixnums 或 floats),值是引用。这与大多数托管语言(例如 Java、Python、JS)的工作方式相同。
在LET
-form 中,变量OBJ
的值是对列表的引用,而不是列表本身。该引用是按值传递的,因此在函数内部,参数LST
的值是对同一列表的另一个引用。
在FOO
中,引用值被替换为NIL
,但是之前引用的列表完全没有被触及。在BAR
中,从堆中检索列表并将其CAR
替换为NIL
。由于OBJ
持有对同一列表的引用,因此修改也会对其产生影响。
【讨论】:
感谢您的回答。有一件事还不是很清楚。我可以自己决定,setf
是否应该跟随一个指针,从堆中检索一个对象并修改它,或者它应该只修改它的绑定?如何修改函数foo
,使其参数设置为NIL?
如果你使用一个普通的变量名作为SETF
的位置,它总是只会修改变量,而不是被引用的对象。要修改对象,必须使用一些访问器(例如CAR
、CDR
、AREF
等)。要让FOO
像你想要的那样工作,你必须将列表包装在某种盒子对象中,然后你可以修改(例如零秩数组或结构),但更惯用的 Lisp 只返回修改后的根据Rainer Joswig的对象。【参考方案2】:
对于某些操作,通常的样式是这样的:
(let ((obj (list "a" "b")))
(setf obj (nbar obj)) ; nbar is destructively modifying the list argument
(foo obj))
上面使用SETF
使用返回值来改变obj
的绑定,因为函数nbar
本身是做不到的。
由于NBAR
无法访问词法变量obj
,因此无法更改它,因此应该从NBAR
返回对象,然后将变量设置为结果值——覆盖前一个绑定。
此规则适用的示例:调用sort
、nreverse
、...
【讨论】:
以上是关于Common Lisp:按值传递与按引用传递[重复]的主要内容,如果未能解决你的问题,请参考以下文章