1

在调用破坏性定义时,我遇到了一个奇怪的行为,该定义接收一个局部变量,其类型是一个使用引号创建的列表

破坏功能:

(defun insert-at-pos (pos list elem)
  (if (= pos 0)
      (cons elem list)
      (let ((aux-list (nthcdr (1- pos) list)))
        (setf (rest aux-list) (cons elem (rest aux-list)))
        list)))

错误:局部变量是使用特殊运算符quote创建的列表。

(defun test ()
 (let ((l '(1 2 3)))
   (print l)
   (insert-at-pos 2 l 4)
   (print l))) 

> (test)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test)

(1 2 4 3)
(1 2 4 4 3)
(1 2 4 4 3)

> (test)

(1 2 4 4 3)
(1 2 4 4 4 3)
(1 2 4 4 4 3) 

正确:局部变量是使用函数list 创建的列表

(defun test2 ()
 (let ((l (list 1 2 3)))
   (print l)
   (insert-at-pos 2 l 4)
   (print l)))

或者

(defun test2 ()
 (let ((l '(1 2 3)))
   (print l)
   (setf l (cons (first l) (cons (second l) (cons 4 (nthcdr 2 l)))))
   (print l)))

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

有人知道这种奇怪行为的原因吗?

4

1 回答 1

8

如果您在函数中引用数据,则它是文字数据。Common Lisp 标准中未定义破坏性修改此类文字数据的效果。在您的示例中,所有函数调用都共享相同的文字数据,并且实现不会警告您正在更改它。这就是大多数实现所做的。但是也可以想象一个将所有代码(及其文字数据)放入内存的只读部分的实现。

你可以用这个获得时髦的效果。

如果您想破坏性地修改列表而不遇到潜在问题,那么您需要在运行时创建一个新副本。例如通过调用LISTor COPY-LISTLIST将返回一个新的 consed 列表。

也有类似的陷阱。例如,想象一个具有以下定义的文件:

(defvar *foo* '(1 2 3 4 5 6 ... 10000))

(defvar *foo* '(0 1 2 3 4 5 6 ... 10000))

如果您使用文件编译器编译这样的文件,则允许编译器创建一个编译文件,其中两个变量共享文字数据 - 节省空间。如果您要更改任一列表中的元素,则两者都可能会更改。

于 2013-06-03T09:11:37.220 回答