3

我在看Practical Common Lisp这本书,在第22章的脚注5,第284页,我看到了一段让我感到困惑的代码片段。

我知道变量 list 和 tail 有一个共同的列表结构,但我很困惑,因为 tail 在迭代期间每次都会被分配 'new' 的值,为什么 (setf (cdr tail) new) 会影响状态变量列表的?</p>

(do ((list nil) (tail nil) (i 0 (1+ i)))
    ((> i 10) list)
  (let ((new (cons i nil)))
    (if (null list)
        (setf list new)
        (setf (cdr tail) new))
    (setf tail new)))

;;; => (0 1 2 3 4 5 6 7 8 9 10)
4

2 回答 2

3

不变量是tail始终是 的最后一个 cons 单元格list

在每次迭代中,都会创建一个新的 cons 单元格,并将新值保存为car。第一次通过,list设置为这个缺点单元格,tail也是。在所有后续迭代中,新的 cons 单元通过将cdr最后一个单元tail

第一次迭代后:

 [ 0 | nil ]
   ^- list
   ^- tail

秒后:

 [ 0 | -]--->[ 1 | nil ]
   ^- list     ^- tail

第三:

 [ 0 | -]--->[ 1 | -]--->[ 2 | nil ]
   ^- list                 ^- tail
于 2019-05-17T11:12:39.223 回答
2

您示例中的do表单保留了一个指向尾部 cons 的指针,以使将元素附加到列表末尾是一种廉价的操作。否则,将需要一直遍历列表以找到最后一个缺点 - 例如使用appendor nconc。另一种方法是在头部和末尾收集新元素以反转结果列表。

人们会期望LOOP宏通过将更高级别的循环表达式转换为有效的代码来实现一些有效的东西。

宏观形式

(loop for i upto 10 collect i)

可能会扩展为与您的do示例在内部类似的东西。的优点loop是您不需要实现跟踪尾部的逻辑,因为这是宏应该为您做的。

于 2019-05-17T12:01:53.950 回答