目录:
- Hello,world
- A Simple Database
- 语法和语义(待补充)
- 函数(Functions)
- 变量
- 序列变量的基本操作
- 标准宏
- 自定义宏(Macors)
-
数字、字符和字符串
参考文献
7. 标准宏
7.1. WHEN和UNLESS(IF)
在介绍WHEN和UNLESS之前,首先详细介绍下已经用过很多次的IF宏。IF宏的语法为:
(if condition then-form [else-form])
else-form为选择书写部分,如果condition为NIL而没有写else-form的话将直接返回NIL。另外当then-form段不止一句代码时要用PROGN将多段代码括上,例如:
(if (spam-p current-message)
(progn
(file-in-spam-folder current-message)
(update-spam-database current-message)))
这样扩展写起来很不舒服,那么就用到WHEN宏了:
(when (condition)
(form1....)
(form2....))
实际上WHEN宏就是用IF构建的宏:
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body)))
对应WHEN宏的是UNLESS,当条件不满足时执行语句,UNLESS的IF实现如下:
(defmacro unless (condition &rest body)
`(if (not ,condition) (progn ,@body)))
UNLESS语法
(when (condition)
(form1....)
(form2....))
7.2. COND
在其他语言中通常有IF——ELSE IF——ELSE IF——ELSE这样的结构,在lisp中也可以这样写:
(if a
(do-x)
(if b
(do-y)
(do-z)))
如果需要多条语句,那么又要写很多的PROG,因此LISP推出了COND宏。
(cond
(test-1 form*)
...
(test-N form*))
7.3. AND、OR、NOT
与或非三种逻辑结构在判断语句中最为常用,下面是一些样例:
(not nil) ==> T
(not (= 1 1)) ==> NIL
(and (= 1 2) (= 3 3)) ==> NIL
(or (= 1 2) (= 3 3)) ==> T
7.4. 循环(DOLIST和DOTIMES)
Dolist宏可以遍历list中的元素,基本格式如下:
(dolist (var list-form)
body-form*)
当循环开始lisp遍历list-form中的每一个元素,然后执行body-form*中的代码,下面是样例:
CL-USER> (dolist (x '(1 2 3)) (print x))
1
2
3
NIL
当满足某些条件不再需要循环时,可以使用RETURN跳出。
CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return)))
1
2
NIL
DOTIMES宏有点类似于C语言中的for语句,格式和样例如下:
(dotimes (var count-form)
body-form*)
CL-USER> (dotimes (i 4) (print i))
0
1
2
3
NIL
DOTIMES也可以使用RETURN来跳出循环,下面的样例求解1到20的乘法表:
(dotimes (x 20)
(dotimes (y 20)
(format t "~3d " (* (1+ x) (1+ y))))
(format t "~%"))
7.5. 循环DO
DOLIST和DOTIMES是针对一些简单的循环应用,写代码比较方便,如果复杂的循环,还是要使用DO宏。
(do (variable-definition*)
(end-test-form result-form*)
statement*)
Variable-definition段用来定义变量,end-test-form用来判定结束条件,result-form表示结束时执行的内容,statement为每一层循环的代码。
每一条variable-definition格式如下:
(var init-form step-form),var为变量名,init-form为初始值,step-form为下一循环的值。
当end-test-form为T时,执行result-form,最后一条执行语句得到的值作为DO宏的返回值。
下面的语句计算了Fibonacci数字:
? (do ((n 0 (+ 1 n))
(cur 0 next)
(next 1 (+ cur next)))
((= 10 n) cur))
55
但是要技术,虽然利用step-form的方法做循环主体计算也是可以的但是,最好遵循DO的标准模式,在statement*中执行主体计算。
7.6. 非常强大的LOOP宏
最简单的LOOP宏,没有任何变量定义仅有循环体:
(loop
body-form*)
样例:
(loop
(when (> (get-universal-time) *some-future-date*)
(return))
(format t "Waiting~%")
(sleep 60))
下面思考一个例子,如果要收集1到10用do如何实现:
(do ((nums nil) (i 1 (1+ i)))
((> i 10) (nreverse nums))
(push i nums)) ==> (1 2 3 4 5 6 7 8 9 10)
用loop for就简单很多了,这是loop的扩展应用:
(loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)
如果要计算1到10的平方和:
(loop for x from 1 to 10 summing (expt x 2)) ==> 385
统计元音(aeiou)在字符串中出现的次数:
(loop for x across "the quick brown fox jumps over the lazy dog"
counting (find x "aeiou")) ==> 11
计算先前写过的Fibonacci数字:
(loop for i below 10
and a = 0 then b
and b = 1 then (+ b a)
finally (return a))
符号across, and, below, collecting, counting, finally, for, from, summing, then, and to都是loop的关键字用来扩展LOOP功能,将在后面的LOOP章节详细介绍。
8. 自定义宏(Macors)
宏是一段会在编译时生成的代码,例如下面的代码,在函数中用到WHEN宏:
(defun foo (x)
(when (> x 10) (print 'big)))
这段代码的目的是为了更好地给编程者阅读或书写,而实际上when宏的内容会在编译时将when替换掉,when宏的定义是这样的:
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body)))
那么替换后foo代码实际上是这样的:
(defun foo (x)
(if (> x 10) (progn (print 'big)))
8.1. 定义宏DEFMACRO
通过DEFMACRO来定义新的宏(DEFMACRO全称Definition for Mac, Read Only。Mac是实现宏功能的人的名字)。
(defmacro name (parameter*)
"Optional documentation string."
body-form*)
定义宏的格式基本与函数相同,总的来说编写宏的过程如下:
1、向宏和它应该扩展的代码写入一个示例调用,反之亦然。
2、在示例代码中将生成的扩展代码写出来。
3、确保宏抽象不缺失。
8.2. 样例: do-primes
do-primes函数用来输出从start到end中的所有素数。那么可以这样写:
(defun primep (number)
(when (> number 1)
(loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))
(defun do-primes (start end)
(loop for p from start to end do
(when (primep p) (format t "~d " p))))
(do-primes 0 20)
2 3 5 7 11 13 17 19
但是这样写,我们能对素数做的就仅仅是输出了,而且输出的格式也改变不了,如果想对得到的素数做一些其他的事情呢?并且是想做什么就做什么呢?
在简易数据库的联系中,我们了解了宏的功能是可以拼接代码,并且可以执行作为宏参数传递进去的代码。那么现在讲do-primes改成宏,为了实现可以对每一个素数操作,那么要将loop for p中的p通过参数来命名,(format t “~d ” p)这句功能代码也通过参数来传递。
do-primes宏的参数就要有(var start end)以及&rest body组成,在循环中通过`符号来实现代码拼接:
`(loop for ,var from ,start to ,end do
(when (primep ,var) ,@body)))
最终得到的宏函数为:
(defmacro do-primes ((var start end) &rest body)
`(loop for ,var from ,start to ,end do
(when (primep ,var) ,@body)))
现在实现将0到100的素数相加,并在每找到一个素数时输出素数以及当前的和,很神奇吧:
? (setf sum-primes 0)
? (do-primes (p 0 100) (setf sum-primes (+ sum-primes p)) (format t " add primes ~d sum is ~d ~%" p sum-primes))
add primes 2 sum is 2
add primes 3 sum is 5
add primes 5 sum is 10
add primes 7 sum is 17
add primes 11 sum is 28
add primes 13 sum is 41
add primes 17 sum is 58
add primes 19 sum is 77
add primes 23 sum is 100
add primes 29 sum is 129
add primes 31 sum is 160
add primes 37 sum is 197
add primes 41 sum is 238
add primes 43 sum is 281
add primes 47 sum is 328
add primes 53 sum is 381
add primes 59 sum is 440
add primes 61 sum is 501
add primes 67 sum is 568
add primes 71 sum is 639
add primes 73 sum is 712
add primes 79 sum is 791
add primes 83 sum is 874
add primes 89 sum is 963
add primes 97 sum is 1060
9. 数字、字符和字符串
9.1 TABLE 1
CL-USER> 10
10
CL-USER> 20/2
10
CL-USER> #xa
10
9.2 TABLE 2
123 ==> 123
+123 ==> 123
-123 ==> -123
123. ==> 123
2/3 ==> 2/3
-2/3 ==> -2/3
4/6 ==> 2/3
6/3 ==> 2
#b10101 ==> 21
#b1010/1011 ==> 10/11
#o777 ==> 511
#xDADA ==> 56026
#36rABCDEFGHIJKLMNOPQRSTUVWXYZ ==> 8337503854730415241050377135811259267835
9.3 TABLE 3
1.0 ==> 1.0
1e0 ==> 1.0
1d0 ==> 1.0d0
123.0 ==> 123.0
123e0 ==> 123.0
0.123 ==> 0.123
.123 ==> 0.123
123e-3 ==> 0.123
123E-3 ==> 0.123
0.123e20 ==> 1.23e+19
123d23 ==> 1.23d+25
9.4 TABLE 4
#c(2 1) ==> #c(2 1)
#c(2/3 3/4) ==> #c(2/3 3/4)
#c(2 1.0) ==> #c(2.0 1.0)
#c(2.0 1.0d0) ==> #c(2.0d0 1.0d0)
#c(1/2 1.0) ==> #c(0.5 1.0)
#c(3 0) ==> 3
#c(3.0 0.0) ==> #c(3.0 0.0)
#c(1/2 0) ==> 1/2
#c(-6/3 0) ==> -2
9.5 TABLE 5
(+ 1 2) ==> 3
(+ 1 2 3) ==> 6
(+ 10.0 3.0) ==> 13.0
(+ #c(1 2) #c(3 4)) ==> #c(4 6)
(- 5 4) ==> 1
(- 2) ==> -2
(- 10 3 5) ==> 2
(* 2 3) ==> 6
(* 2 3 4) ==> 24
(/ 10 5) ==> 2
(/ 10 5 2) ==> 1
(/ 2 3) ==> 2/3
(/ 4) ==> 1/4
9.6 TABLE 6
(+ 1 2.0) ==> 3.0
(/ 2 3.0) ==> 0.6666667
(+ #c(1 2) 3) ==> #c(4 2)
(+ #c(1 2) 3/2) ==> #c(5/2 2)
(+ #c(1 1) #c(2 -1)) ==> 3
9.7 TABLE 7
(+ (* (floor (/ x y)) y) (mod x y)) === x
(+ (* (truncate (/ x y)) y) (rem x y)) === x
9.8 TABLE 8
(incf x) === (setf x (1+ x)) === (setf x (+ x 1))
(decf x) === (setf x (1- x)) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
(decf x 10) === (setf x (- x 10))
9.9 TABLE 9
(= 1 1) ==> T
(= 10 20/2) ==> T
(= 1 1.0 #c(1.0 0.0) #c(1 0)) ==> T
9.10 TABLE 10
(/= 1 1) ==> NIL
(/= 1 2) ==> T
(/= 1 2 3) ==> T
(/= 1 2 3 1) ==> NIL
(/= 1 2 3 1.0) ==> NIL
9.11 TABLE 11
(< 2 3) ==> T
(> 2 3) ==> NIL
(> 3 2) ==> T
(< 2 3 4) ==> T
(< 2 3 3) ==> NIL
(<= 2 3 3) ==> T
(<= 2 3 3 4) ==> T
(<= 2 3 4 3) ==> NIL
9.12 TABLE 12
(max 10 11) ==> 11
(min -12 -10) ==> -12
(max -1 2 -3) ==> 2
Numeric Analog | Case-Sensitive | Case-Insensitive |
---|---|---|
= | CHAR= | CHAR-EQUAL |
/= | CHAR/= | CHAR-NOT-EQUAL |
< | CHAR< | CHAR-LESSP |
> | CHAR> | CHAR-GREATERP |
<= | CHAR<= | CHAR-NOT-GREATERP |
>= | CHAR>= | CHAR-NOT-LESSP |
Literal | Contents | Comment |
---|---|---|
"foobar" | foobar | Plain string. |
"foo"bar" | foo"bar | The backslash escapes quote. |
"foo\bar" | foo\bar | The first backslash escapes second backslash. |
""foobar"" | "foobar" | The backslashes escape quotes. |
"foo\bar" | foobar | The backslash "escapes" b |
CL-USER> "foo\"bar"
"foo\"bar"
CL-USER> (format t "foo\"bar")
foo"bar
NIL
Numeric Analog | Case-Sensitive | Case-Insensitive |
---|---|---|
= | STRING= | STRING-EQUAL |
/= | STRING/= | STRING-NOT-EQUAL |
< | STRING< | STRING-LESSP |
> | STRING> | STRING-GREATERP |
<= | STRING<= | STRING-NOT-GREATERP |
>= | STRING>= | STRING-NOT-LESSP |
(string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7)
(string/= "lisp" "lissome") ==> 3
(string< "lisp" "lisper") ==> 4
(string< "foobar" "abaz" :start1 3 :start2 1) ==> 5 ; N.B. not 2
参考文献
1. 《Practical Common Lisp》,Peter Seibel http://www.gigamonkeys.com/book/
网友评论