在common-lisp中,如何在不更改原始列表的情况下从函数内修改list参数的一部分?

Ros*_*oss 6 lisp function common-lisp parameter-passing pass-by-reference

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

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf n '(x y z))
    n)
Run Code Online (Sandbox Code Playgroud)

如果你调用(测试),即使(修改)返回(xyz),它也会打印(abc).

但是,如果您尝试仅更改列表的一部分,则无法正常工作.我认为这与列表中的内容在内存中相同或者类似的内容有关?这是一个例子:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf (first n) 'x)
    n)
Run Code Online (Sandbox Code Playgroud)

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

Rai*_*wig 12

Lisp列表基于cons单元格.变量就像cons细胞(或其他Lisp对象)的指针.更改变量不会更改其他变量.在所有有缺陷细胞参考的地方都可以看到改变缺陷细胞.

一本好书是Touretzky,Common Lisp:一个温和的符号计算简介.

还有一些软件可以绘制列表和缺陷单元格.

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

(modify (list 1 2 3))
Run Code Online (Sandbox Code Playgroud)

然后,您有三种不同的方式来使用该列表:

净细胞的破坏性修饰

(defun modify (list)
   (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo .
Run Code Online (Sandbox Code Playgroud)

结构共享

(defun modify (list)
   (cons 'bar (rest list)))
Run Code Online (Sandbox Code Playgroud)

Above返回一个与传入列表共享结构的列表:其余元素在两个列表中都相同.

仿形

(defun modify (list)
   (cons 'baz (copy-list (rest list))))
Run Code Online (Sandbox Code Playgroud)

上面的功能BAZ类似于BAR,但没有共享列表单元,因为列表被复制.

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

笔记:

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

不要做:(让((l'(abc)))(setf(first l)'bar))

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

也:

介绍变量

像这样

(let ((original (list 'a 'b 'c)))
   (setf (first original) 'bar))
Run Code Online (Sandbox Code Playgroud)

或者像这样

(defun foo (original-list)
   (setf (first original-list) 'bar))
Run Code Online (Sandbox Code Playgroud)

永远不要SETF一个未定义的变量.


Sva*_*nte 7

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

在这两种情况下,持有的列表originalmodify作为参数传递给它n.这意味着,original在功能上testn在功能modify现在持有相同的列表,这意味着这两个originaln现在都指向它的第一个元素.

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

在第二种情况下,SETF不会修改n,但列表的第一个元素n指向.这是同一个列表所original指向的,因此,之后,您也可以通过此变量查看修改后的列表.

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


for*_*ran 5

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

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

void modify2(char *p) {
    p[0] = 'h';
}
Run Code Online (Sandbox Code Playgroud)

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