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的位置,它总是只会修改变量,而不是被引用的对象。要修改对象,必须使用一些访问器(例如CARCDRAREF 等)。要让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 返回对象,然后将变量设置为结果值——覆盖前一个绑定。

此规则适用的示例:调用sortnreverse、...

【讨论】:

以上是关于Common Lisp:按值传递与按引用传递[重复]的主要内容,如果未能解决你的问题,请参考以下文章

哪个更快?按引用传递与按值传递 C++

java的按值传递与按引用传递

在 C# 中将 REF 和 OUT 关键字与按引用传递和按值传递一起使用

Java:按值传递与按引用传递

cpp►引用变量,按值与按引用传递返回及销毁

在 C++ 中通过引用与按值将向量传递给函数