You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

do与do*宏执行结果差异及Lisp循环相关问题咨询

Common Lisp中do与do*宏的调试问题解析

1. do与do*的结果差异原因

先看正常工作的do版本代码:

(defun my-reverse (my-list)
  (do ((alist my-list (cdr alist))
       (acc nil (cons (car alist) acc)))
      ((null alist) acc)
    (format t "current: ~a~%" (car alist))))

执行输出:

CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(30 20 10)

替换为do*后代码:

(defun my-reverse (my-list)
  (do* ((alist my-list (cdr alist))
        (acc nil (cons (car alist) acc)))
      ((null alist) acc)
    (format t "current: ~a~%" (car alist))))

执行输出:

CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)

核心差异

do和do*的本质区别是循环变量的更新方式:

  • do采用并行更新:所有变量的步进表达式,都基于本轮循环开始时的变量值计算。当终止条件(null alist)为真时,直接返回acc,不会执行任何变量的步进操作,因此不会出现额外的nil。
  • do*采用串行更新:变量按顺序更新,后面的变量步进会使用前面已经更新后的变量值。在最后一次循环中:
    1. 执行循环体打印30后,先将alist更新为(cdr '(30))即nil
    2. 接着执行acc的步进表达式(cons (car alist) acc),此时alist已经是nil,(car nil)返回nil,导致acc被追加了一个nil,最终结果变成(NIL 30 20)。

2. 提取(car alist)为变量x后的问题

无步进表达式的x版本

代码:

(defun my-reverse (my-list)
  (do* ((alist my-list (cdr alist))
        (x (car alist))
        (acc nil (cons x acc)))
      ((null alist) acc)
    (format t "current: ~a~%" x)))

输出:

CL-USER> (my-reverse '(10 20 30))
current: 10
current: 10
current: 10
(10 10 10)

原因

do*中如果变量没有指定步进表达式,其值会保持初始化时的固定值不变。这里x的初始值是(car '(10 20 30))即10,后续循环中没有更新,所以每次累积的都是10。

添加步进表达式后的版本

代码:

(defun my-reverse (my-list)
  (do* ((alist my-list (cdr alist))
        (x (car alist) (car alist))
        (acc nil (cons x acc)))
      ((null alist) acc)
    (format t "current: ~a~%" x)))

输出:

CL-USER> (my-reverse '(10 20 30))
current: 10
current: 20
current: 30
(NIL 30 20)

原因

问题还是出在do*的串行更新逻辑:最后一次循环执行完打印30后,会先更新alist为nil,再更新x为(car nil)即nil,最后更新acc为(cons nil '(30 20)),导致结果多了一个nil。

3. 循环体中修改循环变量的错误

代码:

(defun my-reverse (my-list)
  (do* ((alist my-list (cdr alist))
        (x (car alist) (car alist))
        (acc nil))
      ((null alist) acc)
    ((setf acc (cons x acc))
     (format "current: ~a~%")
     )))

错误提示:illegal function call,指向(setf acc (cons x acc))

原因

这是语法错误,do/do*的循环体是一个或多个表达式的序列,不需要用额外的括号包裹。你把循环体写成((setf ...) (format ...)),相当于试图将(setf ...)的返回值当作函数调用,而setf的返回值不是函数,因此触发非法函数调用错误。

正确写法

循环体允许修改循环变量,只需去掉外层括号即可:

(defun my-reverse (my-list)
  (do* ((alist my-list (cdr alist))
        (x (car alist) (car alist))
        (acc nil))
      ((null alist) acc)
    (setf acc (cons x acc))
    (format t "current: ~a~%" x)))

执行后会得到正确结果(30 20 10)。


内容的提问来源于stack exchange,提问作者7stud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:27:03