我对 Common Lisp 的破坏性 DELETE 函数有点困惑。它似乎按预期工作,除非该项目是列表中的第一项:
CL-USER> (defvar *test* (list 1 2 3))
*TEST*
CL-USER> (delete 1 *test*)
(2 3)
CL-USER> *test*
(1 2 3)
CL-USER> (delete 2 *test*)
(1 3)
CL-USER> *test*
(1 3)
我对 Common Lisp 的破坏性 DELETE 函数有点困惑。它似乎按预期工作,除非该项目是列表中的第一项:
CL-USER> (defvar *test* (list 1 2 3))
*TEST*
CL-USER> (delete 1 *test*)
(2 3)
CL-USER> *test*
(1 2 3)
CL-USER> (delete 2 *test*)
(1 3)
CL-USER> *test*
(1 3)
请记住,这DELETE
应该适用于列表,而不是变量。DELETE
被传递给列表(指向第一个缺点的指针)而不是变量。
由于delete
无权访问变量*test*
,因此无法更改它。这里的“它”是指它的绑定。*test*
将指向与以前相同的缺点单元格。唯一可以更改的是 cons 单元格的内容或第一个 cons 单元格指向的其他 cons 单元格的内容。
可以肯定的是,无论做什么DELETE
,*test*
都将始终指向同一个 cons 单元格。
从中得出什么?如果你想*test*
指向删除操作的结果,那么你必须明确地这样说:
(setq *test* (delete 1 *test*))
“破坏性”并不意味着“就地运作”。这意味着操作值的结构可能会以某种任意且通常未定义的方式进行修改。在某些情况下,这可能会产生效果,就好像它“就位”一样。然而,这通常不能被依赖。
如果您使用破坏性运算符,您是在告诉编译器您对操作完成后变量的先前值不感兴趣。您应该假设该值在之后变得无法识别并且不再使用它。相反,您应该使用操作的返回值。
(let ((a (list 1 2 3)))
(let ((b (delete 2 a)))
(frob b))
a)
=> You were eaten by a grue.
如果您不确定破坏性操作的安全性,请使用它们的非破坏性对应物(remove
在这种情况下)。
(let ((a (list 1 2 3)))
(let ((b (remove 2 a)))
(frob b))
a)
=> (1 2 3)
如果你真的想修改一个变量的内容,将它们设置为操作的返回值:
(let ((a (list 1 2 3)))
(setf a (delete 2 a))
a)
=> (1 3)
DELETE
通过将CDR
列表的前一个 cons 单元格的 更改为指向要删除的元素之后的单元格来工作。但是,如果您要删除列表的第一个元素,则没有以前的 cons 单元格可以修改。
尽管此实现实际上并未由语言标准指定,但它实际上是每个实现的工作方式。
由于在删除列表的第一个元素时没有要修改以前的 cons 单元格,因此它只返回第二个 cons 单元格。因此,即使DELETE
允许修改列表,您仍然必须将结果分配给您的变量以处理这种情况。另外,应该强调的是,破坏性行为仅在标准允许的范围内,而不是必需的。因此,某些实现可能根本不会破坏性地实现它,您也必须考虑到这一点。
即使DELETE
通过修改 CAR 而不是 CDR 来工作,仍然存在无法破坏性的情况:删除列表的所有元素,例如
(setq *test* (list 'a 'a))
(delete 'a *test*)
这会导致一个空列表,即NIL
。但*test*
仍然包含作为原始列表头部的 cons 单元格,并且无法DELETE
更改它。所以你必须这样做:
(setq *test* (delete 'a *test*))
设置*test*
为NIL
。