有幾種類型的突變。一個是對象,您可以在其中將car
或cdr
或cons
更改爲不同的內容。cons
將在前後具有相同的地址,因此指向它的每個變量都將指向相同,但對象被更改。
(defparameter *mutating-obj* (list 1))
(defparameter *mutating-obj2* *mutating-obj*)
(setf (cdr *mutating-obj*) '(2))
在這裏你改變對象,所以這兩個變量仍然指向相同的值已經改變。在評估其中的任何一個時,您會看到(1 2)
。 要知道,因爲我們改變對象,所以開始時它的值永遠不會是()
,因爲它不是可以被突變的car
和cdr
。
對於變量上的setf
,您可以將變量視爲地址位置。因此setf
將改變該位置而不是該值本身。
(defparameter *var1* '(1 2 3))
(defparameter *var2* *var1*)
現在我們有兩個變量指向同一個列表。如果我這樣做:
(push 0 *var2*)
然後*var2*
得到了它的指針改變,使其指向從0開始一個新的列表,並有前值的尾部。這並不會改變*var1*
,它仍然指向之前的值*var2*
。
當你用一個值調用一個函數時,該值被綁定爲一個新變量,並且對其執行push
將做同樣的事情,改變該變量,而不是其他變量發生指向相同的值。
push
的常見用法是從一個空列表開始,並向其中添加元素。設置爲car
和cdr
不適用於將空列表更改爲具有給定值的一個元素列表。所有指向nil
變量將不會就此改變使用rplacd
((setf (cdr var) ...)
)的方法工作,如果你的數據結構必須是從未使用過頭元素:
(defun make-stack()
(list 'stack-head))
(defun push-stack (element stack)
(assert (eq (car stack) 'stack-head))
(setf (cdr stack) (cons element (cdr stack)))
stack)
(defun pop-stack (stack)
(let ((popped (cadr stack)))
(setf (cdr stack) (cddr stack))
popped))
然而,這並不工作,除非你專門設計它所以push
真的需要改變變量,而不是從那以後它的價值永遠。 (除了認爲它改變數值的初學者)
零條件可以通過一些額外的測試來避免。 – gholk
@gholk:如果你沒有變量可訪問性,怎麼會這樣呢? –
@gholk問題是'nil'是一個你不能改變的原子。你不能寫一個'mypush'函數,使得'(mypush 1 nil)'將'nil'變成'(1)'。該函數當然可以返回該對象,但調用者必須捕獲該返回值並更新所有包含該列表的地方。 – Kaz