源代碼下載:?commonlisp-cn.lisp
ANSI Common Lisp 是一個廣泛通用于各個工業(yè)領(lǐng)域的、支持多種范式的編程語言。 這門語言也經(jīng)常被引用作“可編程的編程語言”(可以寫代碼的代碼)。
免費的經(jīng)典的入門書籍《實用 Common Lisp 編程》
另外還有一本熱門的近期出版的?Land of Lisp.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 0\. 語法
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 一般形式
;; Lisp有兩個基本的語法單元:原子(atom),以及S-表達(dá)式。
;; 一般的,一組S-表達(dá)式被稱為“組合式”。
10 ; 一個原子; 它對自身進(jìn)行求值
:THING ;同樣是一個原子;它被求值為一個符號 :thing
t ;還是一個原子,代表邏輯真值。
(+ 1 2 3 4) ; 一個S-表達(dá)式。
'(4 :foo t) ;同樣是一個S-表達(dá)式。
;;; 注釋
;; 一個分號開頭的注釋表示僅用于此行(單行);兩個分號開頭的則表示一個所謂標(biāo)準(zhǔn)注釋;
;; 三個分號開頭的意味著段落注釋;
;; 而四個分號開頭的注釋用于文件頭注釋(譯者注:即對該文件的說明)。
#| 塊注釋
可以涵蓋多行,而且...
#|
他們可以被嵌套!
|#
|#
;;; 運行環(huán)境
;; 有很多不同的Common Lisp的實現(xiàn);并且大部分的實現(xiàn)是一致(可移植)的。
;; 對于入門學(xué)習(xí)來說,CLISP是個不錯的選擇。
;; 可以通過QuickLisp.org的Quicklisp系統(tǒng)管理你的庫。
;; 通常,使用文本編輯器和“REPL”來開發(fā)Common Lisp;
;; (譯者注:“REPL”指讀取-求值-打印循環(huán))。
;; “REPL”允許對程序進(jìn)行交互式的運行、調(diào)試,就好像在系統(tǒng)“現(xiàn)場”操作。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 1\. 基本數(shù)據(jù)類型以及運算符
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 符號
'foo ; => FOO 注意到這個符號被自動轉(zhuǎn)換成大寫了。
;; `intern`由一個給定的字符串而創(chuàng)建相應(yīng)的符號
(intern "AAAA") ; => AAAA
(intern "aaa") ; => |aaa|
;;; 數(shù)字
9999999999999999999999 ; 整型數(shù)
#b111 ; 二進(jìn)制 => 7
#o111 ; 八進(jìn)制 => 73
#x111 ; 十六進(jìn)制 => 273
3.14159s0 ; 單精度
3.14159d0 ; 雙精度
1/2 ; 分?jǐn)?shù)
#C(1 2) ; 復(fù)數(shù)
;; 使用函數(shù)時,應(yīng)當(dāng)寫成這樣的形式:(f x y z ...);
;; 其中,f是一個函數(shù)(名),x, y, z為參數(shù);
;; 如果你想創(chuàng)建一個“字面”意義上(即不求值)的列表, 只需使用單引號 ' ,
;; 從而避免接下來的表達(dá)式被求值。即,只“引用”這個數(shù)據(jù)(而不求值)。
'(+ 1 2) ; => (+ 1 2)
;; 你同樣也可以手動地調(diào)用一個函數(shù)(譯者注:即使用函數(shù)對象來調(diào)用函數(shù)):
(funcall #'+ 1 2 3) ; => 6
;; 一些算術(shù)運算符
(+ 1 1) ; => 2
(- 8 1) ; => 7
(* 10 2) ; => 20
(expt 2 3) ; => 8
(mod 5 2) ; => 1
(/ 35 5) ; => 7
(/ 1 3) ; => 1/3
(+ #C(1 2) #C(6 -4)) ; => #C(7 -2)
;;; 布爾運算
t ; 邏輯真(任何不是nil的值都被視為真值)
nil ; 邏輯假,或者空列表
(not nil) ; => t
(and 0 t) ; => t
(or 0 nil) ; => 0
;;; 字符
#\A ; => #\A
#\λ ; => #\GREEK_SMALL_LETTER_LAMDA(希臘字母Lambda的小寫)
#\u03BB ; => #\GREEK_SMALL_LETTER_LAMDA(Unicode形式的小寫希臘字母Lambda)
;;; 字符串被視為一個定長字符數(shù)組
"Hello, world!"
"Benjamin \"Bugsy\" Siegel" ;反斜杠用作轉(zhuǎn)義字符
;; 可以拼接字符串
(concatenate 'string "Hello " "world!") ; => "Hello world!"
;; 一個字符串也可被視作一個字符序列
(elt "Apple" 0) ; => #\A
;; `format`被用于格式化字符串
(format nil "~a can be ~a" "strings" "formatted")
;; 利用`format`打印到屏幕上是非常簡單的
;;(譯者注:注意到第二個參數(shù)是t,不同于剛剛的nil);~% 代表換行符
(format t "Common Lisp is groovy. Dude.~%")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2\. 變量
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 你可以通過`defparameter`創(chuàng)建一個全局(動態(tài))變量
;; 變量名可以是除了:()[]{}",'`;#|\ 這些字符之外的其他任何字符
;; 動態(tài)變量名應(yīng)該由*號開頭與結(jié)尾!
;; (譯者注:這個只是一個習(xí)慣)
(defparameter *some-var* 5)
*some-var* ; => 5
;; 你也可以使用Unicode字符:
(defparameter *AΛB* nil)
;; 訪問一個在之前從未被綁定的變量是一種不規(guī)范的行為(即使依然是可能發(fā)生的);
;; 不要嘗試那樣做。
;; 局部綁定:在(let ...)語句內(nèi),'me'被綁定到"dance with you"上。
;; `let`總是返回在其作用域內(nèi)最后一個表達(dá)式的值
(let ((me "dance with you"))
me)
;; => "dance with you"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3\. 結(jié)構(gòu)體和集合
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 結(jié)構(gòu)體
(defstruct dog name breed age)
(defparameter *rover*
(make-dog :name "rover"
:breed "collie"
:age 5))
*rover* ; => #S(DOG :NAME "rover" :BREED "collie" :AGE 5)
(dog-p *rover*) ; => t ;; ewww)
(dog-name *rover*) ; => "rover"
;; Dog-p,make-dog,以及 dog-name都是由defstruct創(chuàng)建的!
;;; 點對單元(Pairs)
;; `cons`可用于生成一個點對單元, 利用`car`以及`cdr`將分別得到第一個和第二個元素
(cons 'SUBJECT 'VERB) ; => '(SUBJECT . VERB)
(car (cons 'SUBJECT 'VERB)) ; => SUBJECT
(cdr (cons 'SUBJECT 'VERB)) ; => VERB
;;; 列表
;; 所有列表都是由點對單元構(gòu)成的“鏈表”。它以'nil'(或者'())作為列表的最后一個元素。
(cons 1 (cons 2 (cons 3 nil))) ; => '(1 2 3)
;; `list`是一個生成列表的便利途徑
(list 1 2 3) ; => '(1 2 3)
;; 并且,一個引用也可被用做字面意義上的列表值
'(1 2 3) ; => '(1 2 3)
;; 同樣的,依然可以用`cons`來添加一項到列表的起始位置
(cons 4 '(1 2 3)) ; => '(4 1 2 3)
;; 而`append`也可用于連接兩個列表
(append '(1 2) '(3 4)) ; => '(1 2 3 4)
;; 或者使用`concatenate`
(concatenate 'list '(1 2) '(3 4))
;; 列表是一種非常核心的數(shù)據(jù)類型,所以有非常多的處理列表的函數(shù)
;; 例如:
(mapcar #'1+ '(1 2 3)) ; => '(2 3 4)
(mapcar #'+ '(1 2 3) '(10 20 30)) ; => '(11 22 33)
(remove-if-not #'evenp '(1 2 3 4)) ; => '(2 4)
(every #'evenp '(1 2 3 4)) ; => nil
(some #'oddp '(1 2 3 4)) ; => T
(butlast '(subject verb object)) ; => (SUBJECT VERB)
;;; 向量
;; 向量的字面意義是一個定長數(shù)組
;;(譯者注:此處所謂“字面意義”,即指#(......)的形式,下文還會出現(xiàn))
#(1 2 3) ; => #(1 2 3)
;; 使用`concatenate`來將兩個向量首尾連接在一起
(concatenate 'vector #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)
;;; 數(shù)組
;; 向量和字符串只不過是數(shù)組的特例
;; 二維數(shù)組
(make-array (list 2 2))
;; (make-array '(2 2)) 也是可以的
; => #2A((0 0) (0 0))
(make-array (list 2 2 2))
; => #3A(((0 0) (0 0)) ((0 0) (0 0)))
;; 注意:數(shù)組的默認(rèn)初始值是可以指定的
;; 下面是如何指定的示例:
(make-array '(2) :initial-element 'unset)
; => #(UNSET UNSET)
;; 若想獲取數(shù)組[1][1][1]上的元素:
(aref (make-array (list 2 2 2)) 1 1 1)
; => 0
;;; 變長向量
;; 若將變長向量打印出來,那么它的字面意義上的值和定長向量的是一樣的
(defparameter *adjvec* (make-array '(3) :initial-contents '(1 2 3)
:adjustable t :fill-pointer t))
*adjvec* ; => #(1 2 3)
;; 添加新的元素:
(vector-push-extend 4 *adjvec*) ; => 3
*adjvec* ; => #(1 2 3 4)
;;; 不怎么嚴(yán)謹(jǐn)?shù)卣f,集合也可被視為列表
(set-difference '(1 2 3 4) '(4 5 6 7)) ; => (3 2 1)
(intersection '(1 2 3 4) '(4 5 6 7)) ; => 4
(union '(1 2 3 4) '(4 5 6 7)) ; => (3 2 1 4 5 6 7)
(adjoin 4 '(1 2 3 4)) ; => (1 2 3 4)
;; 然而,你可能想使用一個更好的數(shù)據(jù)結(jié)構(gòu),而并非一個鏈表
;;; 在Common Lisp中,“字典”和哈希表的實現(xiàn)是一樣的。
;; 創(chuàng)建一個哈希表
(defparameter *m* (make-hash-table))
;; 給定鍵,設(shè)置對應(yīng)的值
(setf (gethash 'a *m*) 1)
;; (通過鍵)檢索對應(yīng)的值
(gethash 'a *m*) ; => 1, t
;; 注意此處有一細(xì)節(jié):Common Lisp往往返回多個值。`gethash`返回的兩個值是t,代表找到了這個元素;返回nil表示沒有找到這個元素。
;;(譯者注:返回的第一個值表示給定的鍵所對應(yīng)的值或者nil;)
;;(第二個是一個布爾值,表示在哈希表中是否存在這個給定的鍵)
;; 例如,如果可以找到給定的鍵所對應(yīng)的值,則返回一個t,否則返回nil
;; 由給定的鍵檢索一個不存在的值,則返回nil
;;(譯者注:這個nil是第一個nil,第二個nil其實是指該鍵在哈希表中也不存在)
(gethash 'd *m*) ;=> nil, nil
;; 給定一個鍵,你可以指定其對應(yīng)的默認(rèn)值:
(gethash 'd *m* :not-found) ; => :NOT-FOUND
;; 在此,讓我們看一看怎樣處理`gethash`的多個返回值。
(multiple-value-bind
(a b)
(gethash 'd *m*)
(list a b))
; => (NIL NIL)
(multiple-value-bind
(a b)
(gethash 'a *m*)
(list a b))
; => (1 T)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3\. 函數(shù)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 使用`lambda`來創(chuàng)建一個匿名函數(shù)。
;; 一個函數(shù)總是返回其形式體內(nèi)最后一個表達(dá)式的值。
;; 將一個函數(shù)對象打印出來后的形式是多種多樣的...
(lambda () "Hello World") ; => #<FUNCTION (LAMBDA ()) {1004E7818B}>
;; 使用`funcall`來調(diào)用lambda函數(shù)
(funcall (lambda () "Hello World")) ; => "Hello World"
;; 或者使用`apply`
(apply (lambda () "Hello World") nil) ; => "Hello World"
;; 顯式地定義一個函數(shù)(譯者注:即非匿名的)
(defun hello-world ()
"Hello World")
(hello-world) ; => "Hello World"
;; 剛剛上面函數(shù)名"hello-world"后的()其實是函數(shù)的參數(shù)列表
(defun hello (name)
(format nil "Hello, ~a " name))
(hello "Steve") ; => "Hello, Steve"
;; 函數(shù)可以有可選形參并且其默認(rèn)值都為nil
(defun hello (name &optional from)
(if from
(format t "Hello, ~a, from ~a" name from)
(format t "Hello, ~a" name)))
(hello "Jim" "Alpacas") ;; => Hello, Jim, from Alpacas
;; 你也可以指定那些可選形參的默認(rèn)值
(defun hello (name &optional (from "The world"))
(format t "Hello, ~a, from ~a" name from))
(hello "Steve")
; => Hello, Steve, from The world
(hello "Steve" "the alpacas")
; => Hello, Steve, from the alpacas
;; 當(dāng)然,你也可以設(shè)置所謂關(guān)鍵字形參;
;; 關(guān)鍵字形參往往比可選形參更具靈活性。
(defun generalized-greeter (name &key (from "the world") (honorific "Mx"))
(format t "Hello, ~a ~a, from ~a" honorific name from))
(generalized-greeter "Jim") ; => Hello, Mx Jim, from the world
(generalized-greeter "Jim" :from "the alpacas you met last summer" :honorific "Mr")
; => Hello, Mr Jim, from the alpacas you met last summer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4\. 等式
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Common Lisp具有一個十分復(fù)雜的用于判斷等價的系統(tǒng),下面只是其中一部分的例子
;; 若要比較數(shù)值是否等價,使用`=`
(= 3 3.0) ; => t
(= 2 1) ; => nil
;; 若要比較對象的類型,則使用`eql`
;;(譯者注:抱歉,翻譯水平實在有限,下面是我個人的補充說明)
;;(`eq` 返回真,如果對象的內(nèi)存地址相等)
;;(`eql` 返回真,如果兩個對象內(nèi)存地址相等,或者對象的類型相同,并且值相等)
;;(例如同為整形數(shù)或浮點數(shù),并且他們的值相等時,二者`eql`等價)
;;(想要弄清`eql`,其實有必要先了解`eq`)
;;([可以參考](http://stackoverflow.com/questions/547436/whats-the-difference-between-eq-eql-equal-and-equalp-in-common-lisp))
;;(可以去CLHS上分別查看兩者的文檔)
;;(另外,《實用Common Lisp編程》的4.8節(jié)也提到了兩者的區(qū)別)
(eql 3 3) ; => t
(eql 3 3.0) ; => nil
(eql (list 3) (list 3)) ; => nil
;; 對于列表、字符串、以及位向量,使用`equal`
(equal (list 'a 'b) (list 'a 'b)) ; => t
(equal (list 'a 'b) (list 'b 'a)) ; => nil
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5\. 控制流
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 條件判斷語句
(if t ; “test”,即判斷語句
"this is true" ; “then”,即判斷條件為真時求值的表達(dá)式
"this is false") ; “else”,即判斷條件為假時求值的表達(dá)式
; => "this is true"
;; 在“test”(判斷)語句中,所有非nil或者非()的值都被視為真值
(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(GROUCHO ZEPPO)
(if (member 'Groucho '(Harpo Groucho Zeppo))
'yep
'nope)
; => 'YEP
;; `cond`將一系列測試語句串聯(lián)起來,并對相應(yīng)的表達(dá)式求值
(cond ((> 2 2) (error "wrong!"))
((< 2 2) (error "wrong again!"))
(t 'ok)) ; => 'OK
;; 對于給定值的數(shù)據(jù)類型,`typecase`會做出相應(yīng)地判斷
(typecase 1
(string :string)
(integer :int))
; => :int
;;; 迭代
;; 當(dāng)然,遞歸是肯定被支持的:
(defun walker (n)
(if (zerop n)
:walked
(walker (1- n))))
(walker) ; => :walked
;; 而大部分場合下,我們使用`DOLIST`或者`LOOP`來進(jìn)行迭代
(dolist (i '(1 2 3 4))
(format t "~a" i))
; => 1234
(loop for i from 0 below 10
collect i)
; => (0 1 2 3 4 5 6 7 8 9)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6\. 可變性
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 使用`setf`可以對一個已經(jīng)存在的變量進(jìn)行賦值;
;; 事實上,剛剛在哈希表的例子中我們已經(jīng)示范過了。
(let ((variable 10))
(setf variable 2))
; => 2
;; 所謂好的Lisp編碼風(fēng)格就是為了減少使用破壞性函數(shù),防止發(fā)生副作用。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 7\. 類與對象
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 我們就不寫什么有關(guān)動物的類了,下面給出的人力車的類
(defclass human-powered-conveyance ()
((velocity
:accessor velocity
:initarg :velocity)
(average-efficiency
:accessor average-efficiency
:initarg :average-efficiency))
(:documentation "A human powered conveyance"))
;; `defclass`,后面接類名,以及超類列表
;; 再接著是槽的列表(槽有點像Java里的成員變量),最后是一些可選的特性
;; 例如文檔說明“:documentation”
;; 如果超類列表為空,則默認(rèn)該類繼承于“standard-object”類(standard-object又是T的子類)
;; 這種默認(rèn)行為是可以改變的,但你最好有一定的基礎(chǔ)并且知道自己到底在干什么;
;; 參閱《The Art of the Metaobject Protocol》來了解更多信息。
(defclass bicycle (human-powered-conveyance)
((wheel-size
:accessor wheel-size
:initarg :wheel-size
:documentation "Diameter of the wheel.")
(height
:accessor height
:initarg :height)))
(defclass recumbent (bicycle)
((chain-type
:accessor chain-type
:initarg :chain-type)))
(defclass unicycle (human-powered-conveyance) nil)
(defclass canoe (human-powered-conveyance)
((number-of-rowers
:accessor number-of-rowers
:initarg :number-of-rowers)))
;; 在REPL中對human-powered-conveyance類調(diào)用`DESCRIBE`后結(jié)果如下:
(describe 'human-powered-conveyance)
; COMMON-LISP-USER::HUMAN-POWERED-CONVEYANCE
; [symbol]
;
; HUMAN-POWERED-CONVEYANCE names the standard-class #<STANDARD-CLASS
; HUMAN-POWERED-CONVEYANCE>:
; Documentation:
; A human powered conveyance
; Direct superclasses: STANDARD-OBJECT
; Direct subclasses: UNICYCLE, BICYCLE, CANOE
; Not yet finalized.
; Direct slots:
; VELOCITY
; Readers: VELOCITY
; Writers: (SETF VELOCITY)
; AVERAGE-EFFICIENCY
; Readers: AVERAGE-EFFICIENCY
; Writers: (SETF AVERAGE-EFFICIENCY)
;; 注意到這些有用的返回信息——Common Lisp一直是一個交互式的系統(tǒng)。
;; 若要定義一個方法;
;; 注意,我們計算自行車輪子周長時使用了這樣一個公式:C = d * pi
(defmethod circumference ((object bicycle))
(* pi (wheel-size object)))
;; pi在Common Lisp中已經(jīng)是一個內(nèi)置的常量。
;; 假設(shè)我們已經(jīng)知道了效率值(“efficiency value”)和船槳數(shù)大概呈對數(shù)關(guān)系;
;; 那么效率值的定義應(yīng)當(dāng)在構(gòu)造器/初始化過程中就被完成。
;; 下面是一個Common Lisp構(gòu)造實例時初始化實例的例子:
(defmethod initialize-instance :after ((object canoe) &rest args)
(setf (average-efficiency object) (log (1+ (number-of-rowers object)))))
;; 接著初構(gòu)造一個實例并檢查平均效率...
(average-efficiency (make-instance 'canoe :number-of-rowers 15))
; => 2.7725887
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8\. 宏
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 宏可以讓你擴展語法
;; 例如,Common Lisp并沒有自帶WHILE循環(huán)——所以讓我們自己來為他添加一個;
;; 如果按照匯編程序的直覺來看,我們會這樣寫:
(defmacro while (condition &body body)
"While `condition` is true, `body` is executed.
`condition` is tested prior to each execution of `body`"
(let ((block-name (gensym)))
`(tagbody
(unless ,condition
(go ,block-name))
(progn
,@body)
,block-name)))
;; 讓我們來看看它的高級版本:
(defmacro while (condition &body body)
"While `condition` is true, `body` is executed.
`condition` is tested prior to each execution of `body`"
`(loop while ,condition
do
(progn
,@body)))
;; 然而,在一個比較現(xiàn)代化的編譯環(huán)境下,這樣的WHILE是沒有必要的;
;; LOOP形式的循環(huán)和這個WHILE同樣的好,并且更易于閱讀。
;; 注意反引號'`',逗號','以及'@'這三個符號;
;; 反引號'`'是一種所謂“quasiquote”的引用類型的運算符,有了它,之后的逗號“,”才有意義。
;; 逗號“,”意味著解除引用(unquote,即開始求值);
;; “@”符號則表示將當(dāng)前的參數(shù)插入到當(dāng)前整個列表中。
;;(譯者注:要想真正用好、用對這三個符號,需要下一番功夫)
;;(甚至光看《實用 Common Lisp 編程》中關(guān)于宏的介紹都是不夠的)
;;(建議再去讀一讀Paul Graham的兩本著作《ANSI Common Lisp》和《On Lisp》)
;; 函數(shù)`gensym`創(chuàng)建一個唯一的符號——這個符號確保不會出現(xiàn)在其他任何地方。
;; 這樣做是因為,宏是在編譯期展開的
;; 而在宏中聲明的變量名極有可能和常規(guī)代碼中使用的變量名發(fā)生沖突。
;; 可以去《實用 Common Lisp 編程》中閱讀更多有關(guān)宏的內(nèi)容。
非常感謝Scheme社區(qū)的人們,我基于他們的成果得以迅速的寫出這篇有關(guān)Common Lisp的快速入門 同時也感謝 -?Paul Khuong?,他提出了很多有用的點評。
“祝福那些將思想鑲嵌在重重括號之內(nèi)的人們。”
更多建議: