C代码的讨论
sds 的答案解决了问题的要点,但看起来您正在模拟的 C 代码中发生的事情确实有点混乱:
我想知道是否有任何方法可以用 LISP 中的指针来模仿 C 行为。在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;
}
代码打印三个,因为a
inmutate
是一个仅存在于 中的变量mutate
。仅仅因为它与全局共享名称a
并不意味着改变一个会改变另一个。这段代码中唯一可以更改mutate
's 变量值的地方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
一个is的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
. sds 给出的建议有一个共同点,即您可以通过使用全局变量符号作为 的位置setf
或使用 来修改全局变量的值symbol-value
。也就是说,在定义之后,例如 (defparameter *a* 3)
both*a*
和(symbol-value '*a*)
are您可以在其中存储新值的位置*a*
。因此,我宁愿写一个带有变量名的宏place
and 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
or write
,而第二个参数应该在第一个参数是 时提供write
,是要存储在该位置的新值。使用 调用时read
,将返回该位置的值。使用 调用时write
,将存储并返回一个新值。
不过,这里的多重评估存在一些问题。例如,如果您要执行以下操作,请记住(print 2)
返回值2
:
(make-pointer (aref some-array (print 2)))
2
每次使用指针读取或写入时,您最终都会打印,这可能是不希望的。我不知道这个问题是否需要解决,但请继续阅读一些可能的方法来避免这种情况。
在对类似问题(How to mutate global variable passing to and mutated inside function?)进行了一些研究之后,值得注意的是 Lisp Machines(运行 Lisp Machine Lisp,而不是 Common Lisp)有一个更像 C 指针的概念,称为locatives,在 Common Lisp 的答案中简要提到 ,引用 value 和 actual value。一旦你知道要搜索的术语,就很容易找到更多关于定位词的信息,包括第 13 章。来自 Lisp 机器手册的定位词和 Common Lisp 的各种重新实现,包括Alan Crowe 的,它以长注释开头,以 (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)
每次都进行评估。该代码当然值得一读!