7

我正在尝试将列表传递给 Lisp 中的函数,并在函数中更改该列表的内容而不影响原始列表。我读过 Lisp 是按值传递的,这是真的,但还有一些我不太明白的事情。例如,此代码按预期工作:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf n '(x y z))
    n)

如果您调用 (test),即使 (modify) 返回 (xyz),它也会打印 (abc)。

但是,如果您尝试仅更改列表的一部分,则不会这样。我认为这与具有相同内容的列表在任何地方的内存中都相同或类似的东西有关?这是一个例子:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf (first n) 'x)
    n)

然后(测试)打印(xbc)。那么如何更改函数中列表参数的某些元素,就好像该列表是该函数的本地一样?

4

4 回答 4

14

Lisp 列表基于 cons 单元格。变量就像指向 cons 单元(或其他 Lisp 对象)的指针。改变一个变量不会改变其他变量。更改 cons 单元格将在所有引用这些 cons 单元格的地方可见。

一本好书是 Touretzky, Common Lisp: A Gentle Introduction to Symbolic Computation

还有一些软件可以绘制列表树和 cons 单元格。

如果您将列表传递给这样的函数:

(modify (list 1 2 3))

那么你有三种不同的方式来使用这个列表:

cons细胞的破坏性修饰

(defun modify (list)
   (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo .

结构共享

(defun modify (list)
   (cons 'bar (rest list)))

上面返回一个与传入列表共享结构的列表:两个列表中的其余元素相同。

复制

(defun modify (list)
   (cons 'baz (copy-list (rest list))))

上面的函数 BAZ 类似于 BAR,但没有共享列表单元格,因为列表是复制的。

毋庸置疑,通常应该避免破坏性修改,除非有真正的理由这样做(比如在值得时节省内存)。

笔记:

永远不要破坏性地修改文字常量列表

Dont' do: (let ((l '(abc))) (setf (first l) 'bar))

原因:该列表可能被写保护,或者可能与其他列表共享(由编译器安排)等。

还:

引入变量

像这样

(let ((original (list 'a 'b 'c)))
   (setf (first original) 'bar))

或者像这样

(defun foo (original-list)
   (setf (first original-list) 'bar))

永远不要 SETF 未定义的变量。

于 2009-09-27T21:32:00.057 回答
7

SETF 修改一个地方n可以是一个地方。n列表指向的第一个元素也可以是一个地方。

在这两种情况下,所持有的列表都作为其参数original传递给。这意味着在函数和函数中现在都持有相同的列表,这意味着两者现在都指向它的第一个元素。modifynoriginaltestnmodifyoriginaln

在 SETFn在第一种情况下修改后,它不再指向该列表,而是指向一个新列表。指向的列表original不受影响。新列表随后由 返回modify,但由于此值未分配给任何内容,因此它会逐渐消失并很快被垃圾收集。

在第二种情况下,SETF 修改 not n,但列表的第一个元素n指向。这是同一个列表original指向,所以,之后,你也可以通过这个变量看到修改后的列表。

要复制列表,请使用COPY-LIST

于 2009-09-27T20:18:16.853 回答
5

它与 C 中的这个示例几乎相同:

void modify1(char *p) {
    p = "hi";
}

void modify2(char *p) {
    p[0] = 'h';
}

在这两种情况下,都会传递一个指针,如果更改指针,则更改指针值的参数副本(它在堆栈上),如果更改内容,则更改指向的任何对象的值.

于 2009-09-28T09:46:20.530 回答
2

您可能会遇到问题,因为即使 Lisp 是按值传递对对象的引用,也像在 Java 或 Python 中一样。您的 cons 单元格包含您修改的引用,因此您修改原始和本地引用。

IMO,您应该尝试以更实用的方式编写函数以避免此类问题。尽管 Common Lisp 是多范式的,但函数式风格是一种更合适的方式。

(defun modify (n) (cons 'x (cdr n))

于 2009-09-27T20:06:21.883 回答