C代码讨论
sds's answer 解决了问题的要点,但您正在模拟的 C 代码中发生的事情看起来确实有点混乱:
我想知道是否有任何方法可以模仿 C 的行为
LISP 中的指针。在C语言中,如果你改变一个变量的值,那
指针指向,它具有全局效果(即该值将是
在函数之外也改变了)。
考虑以下内容,我认为它与您提供的 Lisp 代码最相似:
#include<stdio.h>
int a = 3;
int mutate( int a ) {
return a = 5;
}
int main() {
mutate( a ); /* or mutate( 8 ) or anything other argument */
printf( "%d\n", a ); /* prints 3 */
return 0;
}
代码打印三个,因为mutate 中的a 是一个仅存在于mutate 中的变量。仅仅因为它与全局 a 共享一个名称并不意味着改变一个会改变另一个。在这段代码中,您可以更改mutate 的变量a 的值的唯一位置是mutate。您没有“更改 [a] 指针指向的变量的 [the] 值”的选项。你可以做的就是将指针传递给一个变量的值,通过那个指针修改值,然后观察值中的结果。这将对应于这个 C 代码:
#include<stdio.h>
int a = 3;
int mutate( int *a ) {
return (*a = 5);
}
int main() {
mutate( &a );
printf( "%d\n", a ); /* prints 5 */
return 0;
}
通过结构间接
您也可以在 Common Lisp 中使用任何您喜欢的间接方式来执行此类操作。例如,如果您将a 设为cons 单元格,其car 是3,那么您可以传递cons 并修改其car 的值:
CL-USER> (defparameter *a* (cons 3 nil))
*A*
CL-USER> (defun mutate (cons)
(setf (car cons) 5))
MUTATE
CL-USER> (mutate *a*)
5
CL-USER> (car *a*)
5
不过,Lisp 中没有地址操作符,所以你不能完全模拟 C 代码,如果你想的话,你总是需要以某种方式“包装”值使用这种方法。您可以使用 Common Lisp 中的现有结构,例如 cons 单元格、向量或您能找到的任何其他结构。
一般参考文献
虽然它没有 C 风格的指针,但 Common Lisp 定义了一种非常广泛的方式来引用内存位置以进行读写,称为 Generalized Reference。
广义引用是使用一种形式,有时称为
地方,就好像它是一个可以读写的变量。这
地点的价值是地点形式评估的对象。这
可以使用 setf 更改位置的值。绑定的概念
Common Lisp 中没有定义一个地方,但是一个实现是
允许通过定义这个概念来扩展语言。
在 Common Lisp 中,您可以使用 setf 分配给地点。 suggestions that sds gave 的共同点是,您可以通过使用全局变量符号作为setf 的位置或使用symbol-value 来修改全局变量的值。也就是说,在诸如(defparameter *a* 3) 之类的定义之后,*a* 和(symbol-value '*a*) 都是地方,您可以在其中存储*a* 的新值。结果,我宁愿写一个变量名place和value的宏,这样就清楚任何地方都可以作为参数:
(defmacro mutate (place value)
`(setf ,place ,value))
使用词法闭包模拟指向变量的 C 风格指针
因为词法变量也是位置,所以还有一个尚未考虑的选项。您可以使用词法闭包来创建函数,这些函数将为您提供与 C 样式指针相同的功能。
(defmacro make-pointer (place)
`(lambda (op &optional value)
(ecase op
((read) ,place)
((write) (setf ,place value)))))
(let* ((x 3)
(xp (make-pointer x)))
(funcall xp 'write 5) ; write a new value to x
(list (funcall xp 'read) ; read the value from x through xp
x)) ; read the value from x directly
;=> (5 5)
在此代码中,make-pointer 返回一个可以使用一个或两个参数调用的函数。第一个参数应该是一个符号,read 或write,第二个参数应该在第一个参数是write 时提供,是要存储在该位置的新值。当使用read 调用时,将返回该位置的值。当使用write 调用时,会存储并返回一个新值。
不过,这里的多重评估存在一些问题。例如,如果您要执行以下操作,请记住 (print 2) 返回值 2:
(make-pointer (aref some-array (print 2)))
您最终会打印2每次您使用指针读取或写入,这可能是不希望的。我不知道这个问题是否需要解决,但请继续阅读一些可能的方法来避免这种情况。
在对类似问题 (How to mutate global variable passed to and mutated inside function?) 进行一些研究后,值得注意的是 Lisp Machines(运行 Lisp Machine Lisp,而不是 Common Lisp)有一个更像 C 指针的概念,称为 locatives em>,在Common Lisp, reference to value and actual value的回答中简要提及。一旦您知道要搜索的术语,就很容易找到有关定位词的更多信息,包括 Lisp 机器手册中的 Chapter 13. Locatives 和 Common Lisp 的各种重新实现,包括 Alan Crowe's,它以长注释开头,以 (promising ) 简明扼要:
;;; The basic idea is to use closures
稍后(源代码读起来非常好),你会得到:
;;; It looks as though we are done
;;; now we can translate C code
;;; &x = (addr x), *x = (data x)
但有一个警告
;;; The trouble is, we have a multiple evaluation bug.
Crowe 继续展示了如何使用 get-setf-expansion 创建函数来记住如何访问该位置并将值存储到该位置,而无需每次都评估 (print 2)。该代码当然值得一读!