- 列表(Lists)
- 序列(Sequences)
- 断言:
every,some…… - 函数合集
- length(sequence)
- elt(sequence, index)
- count(foo, sequence)
- subseq(sequence start, [end])
- sort, stable-sort(sequence, test [, key function])
- find, position(foo, sequence)
- find-if, find-if-not, position-if, position-if-not(test sequence)
- search(sequence-a, sequence-b)
- substitude, nsubstitute[if, if-not]
- sort, stable-sort, merge
- replace(sequence-a, sequence-b)
- remove, delete(foo sequence)
- mapping(
mapmapcarremove-if[-not]……) - reduce(function, sequence)
- 使用变量创建列表
- 断言:
- 集合(Set)
- 数组和向量(Array、vectors)
- 哈希表(Hash Table)
- Alist
- Plist
- 结构体(Structres)
- 树
列表(Lists)
构建列表
最基本的构建列表的元素是 cons,其中 cons 结构中有两个元素,第一个叫做 car,第二个叫做 cdr
(cons 1 2);; => (1 . 2) ;; representation with a point, a dotted pair.(car (cons 1 2));; => 1(cdr (cons 1 2));; => 2
cons 的构造可以这样理解
[o|o]--- 2|1
当 cons 的第二个元素(cdr)指向的是另外一个 cons,同时这个 cons 的第二个元素是 nil 的话,这样就构成了一个列表。
(cons 1 (cons 2 nil));; => (1 2)(car (cons 1 (cons 2 nil)));; => 1(cdr (cons 1 (cons 2 nil)));; => (2)
这个列表的结构如下:
[o|o]---[o|/]| |1 2
有注意到列表的输出中间没有一个点吗?Lisp 的解释器能够区别这个规则。
当然,也可以通过 list 直接构建一个列表
(list 1 2);; => (1 2)
或者是通过单引号
'(1 2);; => (1 2)
这个是 (quote (1 2)) 的简写
循环列表
cons 结构中的 car 和 cdr 可以指向其他的结构,包括其 cons 本身或是同一个列表中的其他元素。因此,就可以定义像循环列表这样的指向自身的结构。
在构建循环列表之前,需要将 print-circle 这个变量的值设为 T,这样解释器就能够识别循环列表了。
(setf *print-circle* t)
这样,就可以定义一个构建循环列表的函数了
(defun circular! (items)"Modifies the last cdr of list ITEMS, returning a circular list"(setf (cdr (last items)) items))(circular! (list 1 2 3));; => #1=(1 2 3 . #1#)(fifth (circular! (list 1 2 3)));; => 2
list-length这个函数能够识别出循环列表,然后返回 nil。
在解释器中可以使用 [#n=](http://www.lispworks.com/documentation/HyperSpec/Body/02_dho.htm) 直接构建循环列表。其中 n 是一个未被使用过的十进制整数,#n# 可以指向本身
'#42=(1 2 3 . #42#);; => #1=(1 2 3 . #1#)
注意,解释器中输入的 n=42 会被销毁掉,解释器会重新定义一个新的标签(n=1)。
深入理解循环表达式:Let over Lambda
car/cdr 或者 first/rest 以及 second 到 tenth
(car (cons 1 2)) ;; => 1(cdr (cons 1 2)) ;; => 2(first (cons 1 2)) ;; => 1(first '(1 2 3)) ;; => 1(rest '(1 2 3)) ;; => (2 3)
可以通过 setf 来进行赋值。
last、butlast、nbutlast(n为可选参数)
返回列表中的最后一个(或是倒数第 n 个) cons 结构。
(last '(1 2 3));; => (3)(car (last '(1 2 3)) ) ;; or (first (last …));; => 3(butlast '(1 2 3));; => (1 2)
在 Alexandria 包中, lastcar 等价于 (first (last ...))。
(alexandria:lastcar '(1 2 3));; => 3
倒序(reverse、nreverse)
reverse 和 nreverse 会返回一个序列的倒序。其中 nreverse 会破坏原有序列的结构,N 是 non-consing ,表示该函数不会去申请新的 cons 结构,通常是直接在原有的序列上做改动。
(defparameter mylist '(1 2 3));; => (1 2 3)(reverse mylist);; => (3 2 1)mylist;; => (1 2 3)(nreverse mylist);; => (3 2 1)mylist;; => (1)
追加(append)
append 可以接受多个列表参数,然后返回一个包含所有参数的列表。
(append (list 1 2) (list 3 4));; => (1 2 3 4)
栈操作(push pop)
push 将值放在列表的最前面,然后把得到的新列表存在一个位置并打印输出
defparameter mylist '(1 2 3))(push 0 mylist);; => (0 1 2 3)
(defparameter x '(a (b c) d));; => (A (B C) D)(push 5 (cadr x));; => (5 B C)x;; => (A (5 B C) D)
push 等价于 (setf place (cons item place)) ,其中子结构 (cons item place) 中 place 只会求值一次,同时 item 的求值要在 palce 之前。
pop 与 push 相反,会将列表中的第一个元素取出,同时也会破坏列表的结构。
索引(nthcdr)
当 first、second … tenth 不够用时,可以使用 nthcdr
(defparameter mylist '(1 2 3 4 5 6 7 8 9 10 11))(nthcdr 10 mylist)
car/cdr 嵌套(cadr,caadr……)
(caar (list 1 2 3)) ==> error(caar (list (list 1 2) 3)) ==> 1(cadr (list (list 1 2) (list 3 4))) ==> (3 4)(caadr (list (list 1 2) (list 3 4))) ==> 3
destructuring-bind
destructuring-bind 可以将参数绑定到列表中
(destructuring-bind (x y z) (list 1 2 3)(list :x x :y y :z z));; => (:X 1 :Y 2 :Z 3)
同时也可以匹配到子列表
(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)(list :x x :y1 y1 :y2 y2 :z z));; => (:X 1 :Y1 2 :Y2 20 :Z 3)
参数列表中也可以使用 &optional、&rest、key和&whole
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)(list :x x :y1 y1 :y2 y2 :z z));; => (:X 1 :Y1 2 Y2: NIL :Z 3)
(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)(list :x x :y y :z z));; => (:X 3 :Y 2 :Z 1)
(destructuring-bind (&whole whole-list &key x y z) (list :z 1 :y 2 :x 3)(list :x x :y y :z z :whole whole-list));; => (:X 3 :Y 2 :Z 1 :WHOLE-LIST (:Z 1 :Y 2 :X 3))
(destructuring-bind (&key a (b :not-found) c&allow-other-keys)'(:c 23 :d "D" :a #\A :foo :whatever)(list a b c));; => (#\A :NOT-FOUND 23)
如果需要进行模式匹配的话,见 模式匹配 章节
断言(null,listp)
null 等价于 not,但是 null 的格式更好listp 判断对象是 cons 还是 nil
还有一些其他的序列的断言: ldiff tailp list* make-list fill revappend nreconc consp atom
(make-list 3 :initial-element "ta");; => ("ta" "ta" "ta")(make-list 3);; => (NIL NIL NIL)(fill * "hello");; => ("hello" "hello" "hello")
member (elt, list)
返回匹配到列表元素及其后的元素,可以使用 :test :test-not :key 这样的参数
(member 2 '(1 2 3));; (2 3)
替换
subst 和 subst-if 会搜索匹配列表中的元素,并将匹配到的元素进行替换
(subst 'one 1 '(1 2 3));; => (ONE 2 3)(subst 'one 1 '(1 2 1));; => (ONE 2 ONE)(subst '(1 . one) '(1 . 1) '((1 . 1) (2 . 2) (3 . 3)) :test #'equal);; ((1 . ONE) (2 . 2) (3. 3))
sublis 是同时替换多个对象,可以使用 :test 和 :key
(sublis '((x . 10) (y . 20))'(* x (+ x y) (* y y)));; (* 10 (+ 10 20) (* 20 20))(sublis '((t . "foo"))'("one" 2 ("three" (4 5))):key #'stringp);; ("foo" 2 ("foo" (4 5)))
序列(Sequences)
列表(list)和向量(vectors)(因此字符窜(string))都是序列。
大部分序列的函数都可以使用关键词参数,且都是可选的,放置顺序也比较随意。
特别注意 :test 参数,该参数默认的函数是 eql(字符串的话是 :equal)
:key 参数需要传递 nil 或是函数。
(find x y :key 'car)
和 (assoc* x y) 相似,在列表中搜索元素,检查列表中元素的 car 是否于 x 相等, 而不是列表中的元素等于 x。如果 :key 没有写,或者为 nil 时,过滤函数就是 nil。
(defparameter my-alist (list (cons 'foo "foo")(cons 'bar "bar")));; => ((FOO . "foo") (BAR . "bar"))(find 'bar my-alist);; => nil(find 'bar my-alist :key 'car);; => (BAR . "bar")(find (car my-alist) my-alist :key 'nil);; => (FOO . "foo")(find (car my-alist) my-alist);; => (FOO . "foo")
使用 lambda 函数
(find 'bar my-alist :key (lambda (it) (car it)))
注:cl21标准中,可将 lambda 简写:
(find 'bar my-alist :key ^(car %))(find 'bar my-alist :key (lm (it) (cat it)))
断言:every,some……
every, notevery (test, sequence) :返回 nil 或 t。相对应的,对序列的任何一组对应元素进行一次测试,返回 nil。
(defparameter foo '(1 2 3))(every #'evenp foo);; => NIL(some #'evenp foo);; => T(defparameter str '("foo" "bar" "team"))(every #'stringp str);; => T(some #'(lambda (it) (= 3 (length it))) str);; => T(some ^(= 3 (length %)) str) ;; in CL21;; => T
some、notany(test, sequence):如果测试结果中又一个为 t,返回 t,否则为 nil。
mismatch(sequence-a, sequence-b):返回 sequence-a 在 sequence-b 中第一次匹配后的位置。如果sequence-a 和 sequence-b 完全匹配的话,返回 NIL。可以使用参数::from-end bool、start1、start2 以及 :end[1, 2]
函数合集
在 Alexandria包中定义了很多序列相关的函数:starts-with,ends-with,ends-with-subseq,length=,emptyp……
length(sequence)
elt(sequence, index)
注意,这里第一个参数是序列
count(foo, sequence)
返回 sequences 中匹配了 foo 的个数。额外参数::from-end,:start,:end
subseq(sequence start, [end])
在产生的结果和 sequence 的长度相同时,可以使用 setf。
sort, stable-sort(sequence, test [, key function])
该函数会破坏参数的结构,所以最好对拷贝的序列做操作
(sort (copy-seq seq) :test #'string<)
find, position(foo, sequence)
(find 20 '(10 20 30));; 20(position 20 '(10 20 30))
find-if, find-if-not, position-if, position-if-not(test sequence)
search(sequence-a, sequence-b)
返回 sequence-a 在 sequence-b 中的位置,若不匹配,则为 NIL。支持 from-end,end1/2等参数。
substitude, nsubstitute[if, if-not]
(substitute #\o #\x "hellx") ;; => "hello"(substitute :a :x '(:a :x :x)) ;; => (:A :A :A)(substitude "a" "x" '("a" "x" "x") :test #'string=) ;; => ("a" "a" "a")
sort, stable-sort, merge
replace(sequence-a, sequence-b)
remove, delete(foo sequence)
delete 是 remove 的回收版。支持 :start/end,:key,:count。
(remove "foo" '("foo" "bar" "foo") :test 'equal);; => ("bar")
mapping(map mapcar remove-if[-not]……)
(defparameter foo '(1 2 3))(map 'list (lambda (it) (* 10 it)) foo)
reduce(function, sequence)
特殊参数 :initial-value
(reduce '- '(1 2 3 4));; => -8(reduce '- '(1 2 3 4) :initial-value 100);; => 90
使用变量创建列表
这是 反引用(backquote) 的用法之一
(defparameter *var* "bar");; First try:'("foo" *var* "baz") ;; no backquote;; => ("foo" *VAR* "baz") ;; nope`("foo" ,*var* "baz") ;; backquote, comma;; => ("foo" "bar" "baz") ;; good
反引号标志着只是插入的,逗号表示使用变量的值。
如果变量是一个列表的话:
(defparameter *var* '("bar" "baz"));; First try:`("foo" ,*var*);; => ("foo" ("bar" "baz")) ;; nested list`("foo" ,@*var*) ;; backquote, comma-@ to;; => ("foo" "bar" "baz")
集合(Set)
交集:intersection
(defparameter list-a '(0 1 2 3))(defparameter list-b '(0 2 4))(intersection list-a list-b);; => (2 0)
差集:set-difference
(set-difference list-a list-b);; => (3 1)(set-difference list-b list-a);; => (4)
并集:union
(union list-a list-b);; => (3 1 0 2 4) ;; order can be different in your lisp
补集:set-exclusive-or
(set-exclusive-or list-a list-b);; => (4 3 1)
数组和向量(Array、vectors)
数组
数组的特性是:访问时间为常数(θ(1))
简单的数组既不可替换(:displaced-to,指向以另一个数组),也不可变(:adjust-array),同时也没有填充指针(fill-pointer, 随着元素的增减而进行相应的改变)。
向量 是一维的数组。
make-array(sizes-list :adjustable bool)adjust-array(array, sizes-list, :element-type, :initial-element)aref(array i [j …])aref(array i j k …) 或row-major-aref(array i) 等价于(aref i i i ...)(defparameter myarray (make-array '(2 2 2) :initial-element 1))myarray;; => #3A(((1 1) (1 1)) ((1 1) (1 1)))(aref myarray 0 0 0 );; => 1(setf (aref myarray 0 0 0) 9);; => 9(row-major-aref myarray 0);; => 9
array-demensions(array): 数组的维度列表,即数组中元素的总个数array-demension(array):每维数组的大小(defparameter myarray (make array '(2 2 2)));; => MYARRAYmyarray;; => #3A(((0 0) (0 0)) ((0 0) (0 0)))(array-rank myarray);; => 3(array-dimensions myarray);; => (2 2 2)(array-dimension myarray 0);; => 2(array-total-size myarray);; => 8
向量
(vector 1 2 3);; => #(1 2 3)#(1 2 3);; => #(1 2 3)
vector-push(foo vector)vector-push-extend(foo vector [extension-num])vector-pop(vector)fill-pointer(vector)coerce(vector ‘list):将向量转换成列表
哈希表(Hash Table)
创建哈希表 make-hash-table
(defvar *my-hash* (make-hash-table))(setf (gethash :name *my-hash*) "Eitaro Fukamachi")
获取哈希值
(gethash :name *my-hash*)
当键值对不存在时
(gethash 'bar *my-hash* "default-bar");; => "default-bar";; NIL
获取哈希表中的键值对
(ql:quickload :alexandria);; [ ... ](alexandria:hash-table-keys *my-hash*);; => (:NAME)
添加元素
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (setf (gethash 'one-entry *my-hash*) "one")"one"CL-USER> (setf (gethash 'another-entry *my-hash*) 2/4)1/2CL-USER> (gethash 'one-entry *my-hash*)"one"TCL-USER> (gethash 'another-entry *my-hash*)1/2T
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (setf (gethash 'one-entry *my-hash*) "one")"one"CL-USER> (if (gethash 'one-entry *my-hash*)"Key exists""Key does not exist")"Key exists"CL-USER> (if (gethash 'another-entry *my-hash*)"Key exists""Key does not exist")"Key does not exist"
CL-USER> (setf (gethash 'another-entry *my-hash*) nil)NILCL-USER> (if (gethash 'another-entry *my-hash*)"Key exists""Key does not exist")"Key does not exist"
CL-USER> (if (nth-value 1 (gethash 'another-entry *my-hash*))"Key exists""Key does not exist")"Key exists"CL-USER> (if (nth-value 1 (gethash 'no-entry *my-hash*))"Key exists""Key does not exist")"Key does not exist"
删除元素
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (setf (gethash 'first-key *my-hash*) 'one)ONECL-USER> (gethash 'first-key *my-hash*)ONETCL-USER> (remhash 'first-key *my-hash*)TCL-USER> (gethash 'first-key *my-hash*)NILNILCL-USER> (gethash 'no-entry *my-hash*)NILNILCL-USER> (remhash 'no-entry *my-hash*)NILCL-USER> (gethash 'no-entry *my-hash*)NILNIL
遍历哈希表
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (setf (gethash 'first-key *my-hash*) 'one)ONECL-USER> (setf (gethash 'second-key *my-hash*) 'two)TWOCL-USER> (setf (gethash 'third-key *my-hash*) nil)NILCL-USER> (setf (gethash nil *my-hash*) 'nil-value)NIL-VALUECL-USER> (defun print-hash-entry (key value)(format t "The value associated with the key ~S is ~S~%" key value))PRINT-HASH-ENTRYCL-USER> (maphash #'print-hash-entry *my-hash*)The value associated with the key FIRST-KEY is ONEThe value associated with the key SECOND-KEY is TWOThe value associated with the key THIRD-KEY is NILThe value associated with the key NIL is NIL-VALUE
;;; same hash-table as aboveCL-USER> (with-hash-table-iterator (my-iterator *my-hash*)(loop(multiple-value-bind (entry-p key value)(my-iterator)(if entry-p(print-hash-entry key value)(return)))))The value associated with the key FIRST-KEY is ONEThe value associated with the key SECOND-KEY is TWOThe value associated with the key THIRD-KEY is NILThe value associated with the key NIL is NIL-VALUENIL
;;; same hash-table as aboveCL-USER> (loop for key being the hash-keys of *my-hash*do (print key))FIRST-KEYSECOND-KEYTHIRD-KEYNILNILCL-USER> (loop for key being the hash-keys of *my-hash*using (hash-value value)do (format t "The value associated with the key ~S is ~S~%" key value) )The value associated with the key FIRST-KEY is ONEThe value associated with the key SECOND-KEY is TWOThe value associated with the key THIRD-KEY is NILThe value associated with the key NIL is NIL-VALUENILCL-USER> (loop for value being the hash-values of *my-hash*do (print value))ONETWONILNIL-VALUENILCL-USER> (loop for value being the hash-values of *my-hash*using (hash-key key)do (format t "~&~A -> ~A" key value))FIRST-KEY -> ONESECOND-KEY -> TWOTHIRD-KEY -> NILNIL -> NIL-VALUENIL
获取哈希表键值对的数量:hash-table-count
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (hash-table-count *my-hash*)0CL-USER> (setf (gethash 'first *my-hash*) 1)1CL-USER> (setf (gethash 'second *my-hash*) 2)2CL-USER> (setf (gethash 'third *my-hash*) 3)3CL-USER> (hash-table-count *my-hash*)3CL-USER> (setf (gethash 'second *my-hash*) 'two)TWOCL-USER> (hash-table-count *my-hash*)3CL-USER> (clrhash *my-hash*)#<EQL hash table, 0 entries {48205F35}>CL-USER> (hash-table-count *my-hash*)0
性能
CL-USER> (defparameter *my-hash* (make-hash-table))*MY-HASH*CL-USER> (hash-table-size *my-hash*)65CL-USER> (hash-table-rehash-size *my-hash*)1.5CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))Compiling LAMBDA NIL:Compiling Top-Level Form:Evaluation took:0.27 seconds of real time0.25 seconds of user run time0.02 seconds of system run time0 page faults and8754768 bytes consed.NILCL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))Compiling LAMBDA NIL:Compiling Top-Level Form:Evaluation took:0.05 seconds of real time0.05 seconds of user run time0.0 seconds of system run time0 page faults and0 bytes consed.NIL
CL-USER> (log (/ 100000 65) 1.5)18.099062CL-USER> (let ((size 65)) (dotimes (n 20) (print (list n size)) (setq size (* 1.5 size))))(0 65)(1 97.5)(2 146.25)(3 219.375)(4 329.0625)(5 493.59375)(6 740.3906)(7 1110.5859)(8 1665.8789)(9 2498.8184)(10 3748.2275)(11 5622.3413)(12 8433.512)(13 12650.268)(14 18975.402)(15 28463.104)(16 42694.656)(17 64041.984)(18 96062.98)(19 144094.47)NIL
CL-USER> (defparameter *my-hash* (make-hash-table :size 100000))*MY-HASH*CL-USER> (hash-table-size *my-hash*)100000CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))Compiling LAMBDA NIL:Compiling Top-Level Form:Evaluation took:0.04 seconds of real time0.04 seconds of user run time0.0 seconds of system run time0 page faults and0 bytes consed.NIL
CL-USER> (defparameter *my-hash* (make-hash-table :rehash-size 100000))*MY-HASH*CL-USER> (hash-table-size *my-hash*)65CL-USER> (hash-table-rehash-size *my-hash*)100000CL-USER> (time (dotimes (n 100000) (setf (gethash n *my-hash*) n)))Compiling LAMBDA NIL:Compiling Top-Level Form:Evaluation took:0.07 seconds of real time0.05 seconds of user run time0.01 seconds of system run time0 page faults and2001360 bytes consed.NIL
Alist
Alist: An association list is a list of cons cells.
(defparameter *my-alist* (list (cons 'foo "foo")(cons 'bar "bar")));; => ((FOO . "foo") (BAR . "bar"))
其结构如下:
[o|o]---[o|/]| || [o|o]---"bar"| || BAR|[o|o]---"foo"|FOO
所以,通过其结构来对其进行定义:
(setf *my-alist* '((:foo . "foo")(:bar . "bar")))
Plist
Plist: A property list is simply a list that alternates a key, a value, and so on, where its keys are symbols (we can not set its :test). More precisely, it first has a cons cell whose car is the key, whose cdr points to the following cons cell whose car is the value.
(defparameter my-plist (list 'foo "foo" 'bar "bar"))
[o|o]---[o|o]---[o|o]---[o|/]| | | |FOO "foo" BAR "bar"
(defparameter my-plist (list 'foo "foo" 'bar "bar"));; => (FOO "foo" BAR "bar")(setf (getf my-plist 'foo) "foo!!!");; => "foo!!!"
结构体(Structres)
创建:defstruct
(defstruct personid name age)
设置默认值
(defstruct personid(name "john doe")age)
设置默认类型
(defstruct personid(name "john doe" :type string)age)
(defparameter *me* (make-person))*me*#S(PERSON :ID NIL :NAME "john doe" :AGE NIL)
当类型设置错误时:
(defparameter *bad-name* (make-person :name 123))
Invalid initialization argument::NAMEin call for class #<STRUCTURE-CLASS PERSON>.[Condition of type SB-PCL::INITARG-ERROR]
不使用关键次参数来创建结构体
(defstruct (person (:constructor create-person (id name age)))idnameage)
(create-person 1 "me" 7)#S(PERSON :ID 1 :NAME "me" :AGE 7)
然而,默认的 make-person 不可以使用
(make-person :name "me");; debugger:obsolete structure error for a structure of type PERSON[Condition of type SB-PCL::OBSOLETE-STRUCTURE]
访问 槽(slot)
(person-name *me*);; "john doe"
(setf (person-name *me*) "Cookbook author")(person-name *me*);; "Cookbook author"
断言
在创建结构时,一个断言函数会自动创建
(person-p *me*)T
单继承
(defstruct (female (:include person))(gender "female" :type string))(make-female :name "Lilie");; #S(FEMALE :ID NIL :NAME "Lilie" :AGE NIL :GENDER "female")
局限
修改后,实例不会被更新
(defstruct personid(name "john doe" :type string)ageemail)attempt to redefine the STRUCTURE-OBJECT class PERSONincompatibly with the current definition[Condition of type SIMPLE-ERROR]Restarts:0: [CONTINUE] Use the new definition of PERSON, invalidating already-loaded code and instances.1: [RECKLESSLY-CONTINUE] Use the new definition of PERSON as if it were compatible, allowing old accessors to use new instances and allowing new accessors to use old instances.2: [CLOBBER-IT] (deprecated synonym for RECKLESSLY-CONTINUE)3: [RETRY] Retry SLIME REPL evaluation request.4: [*ABORT] Return to SLIME's top level.5: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1002A0FFA3}>)
如果重新开始的话,使用新的定义,将无法访问 *me*
*me*obsolete structure error for a structure of type PERSON[Condition of type SB-PCL::OBSOLETE-STRUCTURE]
树
tree-equal copy-tree
