我在调用破坏性定义时遇到一种奇怪的行为,接收一个局部变量作为参数,该变量的类型是用list创建的quote。
破坏性功能:
(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)
CORRECT :局部变量是使用函数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)
有人知道这种奇怪行为的原因吗?
答案 0 :(得分:8)
如果在函数中引用数据,则它是文字数据。在Common Lisp标准中,破坏性地修改这些文字数据的效果是不确定的。在您的示例中,所有函数调用共享相同的文字数据,并且实现不会警告您正在更改它。这就是大多数实现所做的。但是也可以想象一个实现将所有代码(及其文字数据)放入内存的只读部分。
你可以用这个来获得时髦的效果。
如果要破坏性地修改列表而不遇到潜在问题,则需要在运行时创建一个新副本。例如,通过调用LIST
或COPY-LIST
。 LIST
将返回一份新的精选列表。
存在类似的陷阱。例如,想象一下具有以下定义的文件:
(defvar *foo* '(1 2 3 4 5 6 ... 10000))
(defvar *foo* '(0 1 2 3 4 5 6 ... 10000))
如果使用文件编译器编译这样的文件,则允许编译器创建编译文件,其中两个变量共享文字数据 - 节省空间。如果要更改任一列表中的元素,则两者都可能会更改。