lisp中getkword输入默认_0基础——lisp学习笔记(二)

目录:

Hello,world

A Simple Database

语法和语义(待补充)

函数(Functions)

变量

序列变量的基本操作

标准宏

自定义宏(Macors)

数字、字符和字符串

参考文献

3. 语法和语义(待补充)

在介绍lisp的语法和语义之前,首先了解一下它与其他语言的不同之处是非常必要的。大都数编程语言,无论是解析型还是编译型,对语法的操作都是像在黑匣子里。你将一串的表达式或语句传递给黑匣子,而程序的执行行为和编译版本都取决于它的编译器或解析器。

当然,在黑匣子中,语言处理器通常分为子系统,每个子系统负责将程序文本翻译成行为或目标代码的一部分。一个典型的划分是将处理器分成三个阶段,每一个阶段连接下一个阶段:词法分析器将字符流分解为符号,并将它们馈送到一个语法分析器中,该解析器根据程序语言的语法生成程序中表达式的树。这棵树被称之为抽象语法树,然后进入一个计算器,把它直接或将它编译成其他语言如机器代码。因为语言处理器是一个黑匣子,处理器使用的数据结构,如符号和抽象语法树,只有编译器或解析器的实现者清楚。

在lisp中事情有一些不同,程序运行结果与编译器和如何书写代码都有关系。与一个黑匣子一步决定程序行为不同,lisp定义了两个黑匣子。一个将文本转换为lisp对象称之为reader,而另一个实现程序中的这些对象的语义,称之为evaluator。

Lisp主要包含两种结构list和atom。

4. 函数(Functions)

Lisp中最基本的三个组成部分为函数(Functions)、变量(Variables)和宏(Macros)。其中Functions是所有编程语言中实现抽象的最基本的机制。实际上宏也是通过函数来实现的,只不过宏是在编译时构建。

在lisp中通过DEFUN宏来定义新的函数,最基本的定义骨架如下:

(defun name (parameter*)

"Optional documentation string."

body-form*)

函数名称可以是任何符号,例如定义++作为一个函数实现输入参数的自加运算。

? (defun ++ (a)

(+ a 1))

++

? (++ 10)

11

函数定义骨架中表示的"Optional documentation string."说明在函数声明下面第一行“”内包括的内容是函数的说明。函数的参数列表有很多种形式,lisp提供了很多复杂提供参数的方式。

4.1. 可选择参数(Optional Parameter)

在lisp函数声明的参数中,可以使用&optional符号声明在此之后的参数是选择性给出的,可选择参数的默认值为NIL,也可以为可选择参数提供默认值。在可选参数的默认值后面增加参数名+”-supplied-p”可以显示表示是否选择给出了该参数的值,例如参数c默认值为3在默认值后加c-supplied-p那么如果c取默认值则c-supplied-p为NIL反之为T,以下为示例:

(defun foo (a b &optional c d) (list a b c d))

(foo 1 2) ==> (1 2 NIL NIL)

(foo 1 2 3) ==> (1 2 3 NIL)

(foo 1 2 3 4) ==> (1 2 3 4)

声明默认值:

(defun foo (a &optional (b 10)) (list a b))

(foo 1 2) ==> (1 2)

(foo 1) ==> (1 10)

判定有默认值的参数是否赋予值:

(defun foo (a b &optional (c 3 c-supplied-p))

(list a b c c-supplied-p))

(foo 1 2) ==> (1 2 3 NIL)

(foo 1 2 3) ==> (1 2 3 T)

(foo 1 2 4) ==> (1 2 4 T)

4.1. 剩余参数(Rest Parameters)

Lisp允许在函数参数中引入&rest符号,表示在其后面的参数数量可以不限制。下面的声明是lisp中format函数与+函数的声明方式,rest符号一定在参数声明的最后:

(defun format (stream string &rest values) ...)

(defun + (&rest numbers) ...)

4.3. 关键字参数

假设我有三个输入参数,但是我只想给第一个和第三个参数赋值怎么办?换句话说指定特定的输入参数值,此时可以用关键字参数来实现,&key符号后面的参数均为关键字参数,可以使用:named的方式为特定参数赋值。

(defun foo (&key a b c) (list a b c))

(foo) ==> (NIL NIL NIL)

(foo :a 1) ==> (1 NIL NIL)

(foo :b 1) ==> (NIL 1 NIL)

(foo :c 1) ==> (NIL NIL 1)

(foo :a 1 :c 3) ==> (1 NIL 3)

(foo :a 1 :b 2 :c 3) ==> (1 2 3)

(foo :a 1 :c 3 :b 2) ==> (1 2 3)

关键字参数同样支持默认值和-supplied-p。

(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))

(list a b c b-supplied-p))

(foo :a 1) ==> (1 0 1 NIL)

(foo :b 1) ==> (0 1 1 T)

(foo :b 1 :c 4) ==> (0 1 4 T)

(foo :a 2 :b 1 :c 4) ==> (2 1 4 T)

也可以为参数强调特别的key名称,而不用:named模式。

(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))

(list a b c c-supplied-p))

(foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)

Lisp函数参数的几种形式可以混合,&optional和&rest配合、&rest和&key配合。

4.4. 函数返回值

RETURN-FROM宏可以用来立刻退出函数,并返回特定值(实际上RETURN-FROM不仅仅用来返回函数,也可以用来返回BLOCK)。

(defun foo (n)

(dotimes (i 10)

(dotimes (j 10)

(when (> (* i j) n)

(return-from foo (list i j))))))

4.5. 函数也是数据

在lisp中函数也是可以作为数据传递的,函数只是用DEFUN定义的对象。FUNCTION操作提供一直获得函数对象的机制,例如:

CL-USER> (defun foo (x) (* 2 x))

FOO

CL-USER> (function foo)

#

’符号实际上就是FUNCTION

CL-USER> #'foo

#

对于函数对象有两个操作FUNCALL和APPLY,当知道函数的具体参数数量时使用FUNCALL,例:

(foo 1 2 3) === (funcall #'foo 1 2 3)

实际上在你写函数时,有时候不知道传递进来的函数名称,但是知道参数数量,这时候才是funcall的用武之地,例:

(defun plot (fn min max step)

(loop for i from min to max by step do

(loop repeat (funcall fn i) do (format t "*"))

(format t "~%")))

CL-USER> (plot #'exp 0 4 1/2)

*

*

**

****

*******

************

********************

*********************************

******************************************************

NIL

APPLY接收任意数量的参数(统一为一个list),并且不限制&optional、&rest和&key。使用方法与FUNCALL类似。

4.6. 匿名函数

匿名函数在并不想为一段代码定义专门的函数时使用(当然,不用函数还不行),例如:

(defun double (x) (* 2 x))

(plot #'double 0 10 1)

函数double与(lambda (x) (* 2 x))功能是一样的,但是仅仅为了这个单独写一个函数可能不值当,那么就用匿名函数,这样代码更简洁,通常匿名函数都很短:

(plot #'(lambda (x) (* 2 x)) 0 10 1)

5. 变量

Lisp支持的变量有两大种类:lexical和dynamic。Lisp与其他语言例如Java或C/C++最大的区别就是lisp变量没有具体的类型,在实现上属于动态类型。

对于变量在函数中的传递,lisp每次调用函数,都会创建新的连接给函数的调用者。如果传递给函数的参数是可变的(mutable)则在函数中的修改会影响参数,否则没有影响。

LET操作用来初始化新的变量。

(let (variable*)

body-form*)

(let ((x 10) (y 20) z)

...)

LET操作定义的变量相当于重新定义局部变量有效范围仅是body-form内,不会对同名全局变量产生影响。

(defun foo (x)

(format t "Parameter: ~a~%" x) ; |

(let ((x 2)) ; |

(format t "Outer LET: ~a~%" x) ; | |

(let ((x 3)) ; | |

(format t "Inner LET: ~a~%" x)) ; | | |

(format t "Outer LET: ~a~%" x)) ; | |

(format t "Parameter: ~a~%" x))

CL-USER> (foo 1)

Parameter: 1

Outer LET: 2

Inner LET: 3

Outer LET: 2

Parameter: 1

NIL

另外还有LET操作,基本与LET相同,尽在定义变量时LET可以引用在LET*中定义较早的变量。

(let* ((x 10)

(y (+ x 10)))

(list x y))

而LET只能这样:

(let ((x 10))

(let ((y (+ x 10)))

(list x y)))

5.1. Lexical变量和闭包(Closures)

变量的作用范围是件神奇的事情

(let ((count 0)) #'(lambda () (setf count (1+ count))))

Count之所以能传递到匿名函数,是因为匿名函数在let方法的体内,而将上面语句定义为参数,每次通过FUNCALL调用时,count就又变成了类似于全局变量的而实际上又是局部变量(因为你如果直接访问count是找不到这个变量的,只有通过函数fn才能找到),这个特性实际上可以为变量提供某种安全机制。

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

CL-USER> (funcall *fn*)

1

CL-USER> (funcall *fn*)

2

CL-USER> (funcall *fn*)

3

5.2. Dynamic,a.k.a Special,Variables

Lisp的全局函数声明有两种方式DEFVAR和DEFPARAMETER:

(defvar *count* 0

"Count of widgets made so far.")

(defparameter *gap-tolerance* 0.001

"Tolerance to be allowed in widget gaps.")

至于defvar与defparameter的不同,暂时只知道defvar可以不赋初始值。定义完的全局变量,就可以在全程序段使用了。

下面是一些对let使用的样例:

(defvar *x* 10)

(defun foo () (format t "X: ~d~%" *x*))

(defun bar ()

(foo)

(let ((*x* 20)) (foo))

(foo))

CL-USER> (bar)

X: 10

X: 20

X: 10

NIL

另外:

(defun foo ()

(format t "Before assignment~18tX: ~d~%" *x*)

(setf *x* (+ 1 *x*))

(format t "After assignment~18tX: ~d~%" *x*))

CL-USER> (bar)

Before assignment X: 11

After assignment X: 12

Before assignment X: 20

After assignment X: 21

Before assignment X: 12

After assignment X: 13

NIL

5.3. 常数

Lisp使用DEFCONSTANT定义全局常数,由于lisp在变量命名上限制非常小,可以使用-或者+等特别标记下该变量时全局常数例如:+c1+。常数不可重定义,不可修改。

5.4. 赋值

Setf是lisp中的基本赋值语句,格式如下:

(setf place value ...)

其他语言的赋值语句如下:

Assigning to ...

Java, C, C++

Perl

Python

... variable

x = 10;

$x = 10;

x = 10

... array element

a[0] = 10;

$a[0] = 10;

a[0] = 10

...hash table entry

--

$hash{'key'} = 10;

hash['key'] = 10

... field in object

o.field = 10;

$o->{'field'} = 10;

o.field = 10

在lisp中实现各种赋值样例如下,aref用来索引数组,gethash用来索引哈希表,field o相当于o.field:

名称

代码

Simple variable:

(setf x 10)

Array:

(setf (aref a 0) 10)

Hash table:

(setf (gethash 'key hash) 10)

Slot named 'field':

(setf (field o) 10)

5.5. 其他修改数据的方法

除setf之外,还有INCF(相当于++)、DECF(相当于--)、PUSH、PUSHNEW、POP。

还有ROTATEF和SHIFTF,其中ROTATEF用来交换两个变量:

(rotatef a b)

对于普通的变量,相当于

(let ((tmp a)) (setf a b b tmp) nil)

SHIFTF命令相当于将参数列表中右侧的值付给左侧的值:

(shiftf a b 10)

相当于

(let ((tmp a)) (setf a b b 10) tmp)

6. 序列变量的基本操作

6.1. 向量(Vectors)和一维数组(Arrays)

可以使用VECTOR函数定义向量,在lisp中向量可以是任意维度的,但是生成后大小是固定的。

(vector) → #()

(vector 1) → #(1)

(vector 1 2) → #(1 2)

(vector 1 2 3 4) → #(1 2 3 4)

MAKE-ARRAY是更常用的方式用来生成数组(或者说高维向量,试想下用vector创建20维的向量,需要打20个0....)。下面的代码生成了大小为5的数组,:initial-element表示以nil初始化每一个元素。

(make-array 5 :initial-element nil) → #(NIL NIL NIL NIL NIL)

:fill-pointer用来定义某初始大小的可变长度数组(向量),下面定义了最大尺寸为5的可调整大小的向量:

(make-array 5 :fill-pointer 0) → #()

使用VECTOR-PUSH函数,向可调整大小向量中添加元素。使用VECTOR-POP函数在堆中弹出元素。

(defparameter *x* (make-array 5 :fill-pointer 0))

(vector-push 'a *x*) → 0

*x* → #(A)

(vector-push 'b *x*) → 1

*x* → #(A B)

(vector-push 'c *x*) → 2

*x* → #(A B C)

(vector-pop *x*) → C

*x* → #(A B)

(vector-pop *x*) → B

*x* → #(A)

(vector-pop *x*) → A

*x* → #()

若要使向量不限制尺寸还需要传递:adjustable参数,使用VECTOR-PUSH-EXTEND向其中添加元素。

(make-array 5 :fill-pointer 0 :adjustable t) → #()

函数LENGTH可以得到向量的大小,ELT可以通过索引得到向量中的数据。

? (defparameter *x* (vector 1 2 3))

*X*

? (length *x*)

3

? (elt *x* 0)

1

;(elt *x* 1) → 2

;(elt *x* 2) → 3

;(elt *x* 3) → error

? (setf (elt *x* 0) 10)

10

? *x*

#(10 2 3)

6.2. 序列的迭代函数(一些常规的索引方法)

针对序列lisp还提供了很多迭代类索引函数:

名称

需要的参数

返回值

COUNT

元素和序列

序列中元素出现的次数

(count 1 #(1 2 1 2 3 1 2 3 4))

3

FIND

元素和序列

找到的元素或NIL

(find 1 #(1 2 1 2 3 1 2 3 4))

1

(find 10 #(1 2 1 2 3 1 2 3 4))

NIL

POSITION

元素和序列

元素的位置或NIL

(position 1 #(1 2 1 2 3 1 2 3 4))

0

REMOVE

元素和序列

删除元素后的序列

remove 1 #(1 2 1 2 3 1 2 3 4))

#(2 2 3 2 3 4)

(remove 1 '(1 2 1 2 3 1 2 3 4))

(2 2 3 2 3 4)

(remove #\a "foobarbaz")

"foobrbz"

SUBSTITUTE

新的元素,元素和序列

替换后的新序列

(substitute 10 1 #(1 2 1 2 3 1 2 3 4))

#(10 2 10 2 3 10 2 3 4)

(substitute 10 1 '(1 2 1 2 3 1 2 3 4))

(10 2 10 2 3 10 2 3 4)

(substitute #\x #\b "foobarbaz")

"fooxarxaz"

参数列表:

参数

描述

:test

用来比较元素的方法(两参数)(或通过:key功能提取的值),默认为:EQL

-

(count "foo" #("foo" "bar" "baz") :test #'string=) → 1

? (defun verstring= (x y)

(format t "Looking at ~s to ~s ~%" x y) (string= x y))

VERSTRING=

? (count "foo" #("foo" "bar" "baz") :test #'verstring=)

Looking at "foo" to "foo"

Looking at "foo" to "bar"

Looking at "foo" to "baz"

1

参数

描述

:key

从实际序列提取关键字的方法(一参数),NIL表示将元素当做is处理,默认为:NIL

-

(find 'c #((a 10) (b 20) (c 30) (d 40)) :key #'first) → (C 30)

-

(find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first) → (A 10)

-

(find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first :from-end t) → (A 30)

? (defun verbose-first (x) (format t "Looking at ~s~%" x) (first x))

VERBOSE-FIRST

? (count 'a #((a 10) (b 20) (a 30) (b 40)) :key #'verbose-first)

Looking at (A 10)

Looking at (B 20)

Looking at (A 30)

Looking at (B 40)

2

参数

描述

:from-end

如果为真,遍历顺序为反向,默认为NIL

-

(find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first :from-end t) → (A 30)

-

(remove #\a "foobarbaz" :count 1 :from-end t) →"foobarbz"

参数

描述

-

-

:count

指示要删除或替换或不表示所有的元素的数目(仅移除和替换)。

:start

子序列的开始索引值(包含)

:end

子序列的结束索引值(不包含)

Lisp提供了一些更高级的索引方法,通过以上基本的方法添加-if或-if-not已实现满足一些复杂判断条件的索引效果,例如:

? (count-if #'evenp #(1 2 3 4 5))

2

? (count-if-not #'evenp #(1 2 3 4 5))

3

? (position-if #'digit-char-p "abcd0001")

4

? (remove-if-not #'(lambda (x) (char= (elt x 0) #\f))

#("foo" "bar" "baz" "foom"))

#("foo" "foom")

(count-if #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) → 2

(count-if-not #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) → 3

(remove-if-not #'alpha-char-p

#("foo" "bar" "1baz") :key #'(lambda (x) (elt x 0))) → #("foo" "bar")

比较特殊的remove还可以添加-duplicates移除重复元素:

(remove-duplicates #(1 2 1 2 3 1 2 3 4)) → #(1 2 3 4)

CONCATENATE函数将多个序列组合为一个序列:

(concatenate 'vector #(1 2 3) '(4 5 6)) → #(1 2 3 4 5 6)

(concatenate 'list #(1 2 3) '(4 5 6)) → (1 2 3 4 5 6)

(concatenate 'string "abc" '(#\d #\e #\f)) → "abcdef"

6.3. 排序与合并(Sort和Merging)

行数SORT与STABLE-SORT提供了两种排序序列的方式,这两种函数都需要两个参数并且返回一个排序好的序列:

(sort (vector "foo" "bar" "baz") #'string

Merg函数将两个序列有序的合并为一个:

(merge 'vector #(1 3 5) #(2 4 6) #'

(merge 'list #(1 3 5) #(2 4 6) #'

6.4. 子序列操作

subseq返回一个序列的子序列:

(subseq "foobarbaz" 3) → "barbaz"

(subseq "foobarbaz" 3 6) → "bar"

Subseq的子序列的修改是可以对原始序列产生影响的:

(defparameter *x* (copy-seq "foobarbaz"))

(setf (subseq *x* 3 6) "xxx") ;子序列和替换序列长度相同

*x* → "fooxxxbaz"

(setf (subseq *x* 3 6) "abcd") ; 替换序列比子序列长,忽略多余

*x* → "fooabcbaz"

(setf (subseq *x* 3 6) "xx") ; 替换序列比子序列短,仅替换两个

*x* → "fooxxcbaz"

可以用FILL函数将序列中的多个元素替换为某一个值,:start和:end参数可以用来限制子序列的行为。

Search函数与POSITION函数类似,但可以在序列中搜索子序列:

(position #\b "foobarbaz") → 3

(search "bar" "foobarbaz") → 3

MISMATCH函数用来找到两个序列第一次出现不匹配的位置,若完全匹配则返回NIL。当然也可以使用from-end来从末尾开始检索。

(mismatch "foobarbaz" "foom") → 3

(mismatch "foobar" "bar" :from-end t) → 3

6.5. 序列的整体判定

EVERY、SOME、NOTANY、NOTEVERY:

(every #'evenp #(1 2 3 4 5)) → NIL

(some #'evenp #(1 2 3 4 5)) → T

(notany #'evenp #(1 2 3 4 5)) → NIL

(notevery #'evenp #(1 2 3 4 5)) → T

(every #'> #(1 2 3 4) #(5 4 3 2)) → NIL

(some #'> #(1 2 3 4) #(5 4 3 2)) → T

(notany #'> #(1 2 3 4) #(5 4 3 2)) → NIL

(notevery #'> #(1 2 3 4) #(5 4 3 2)) → T

6.6. MAPPING函数

要求两个向量每一个元素的乘积得到的新向量:

(1 2 3 4 5).*(10 9 8 7 6)=(10 18 24 28 30)

可以利用MAP函数:

(map 'vector #'* #(1 2 3 4 5) #(10 9 8 7 6)) → #(10 18 24 28 30)

将a、b、c的和保存到a中可以用MAP-INTO:

(map-into a #'+ a b c)

REDUCE函数仅对一个序列进行操作,下面的代码计算出序列的和:

reduce #'+ #(1 2 3 4 5 6 7 8 9 10)) → 55

下面的代码求得一个序列的最大值:

reduce #'+ #(1 2 3 4 5 6 7 8 9 10)) → 10

REDUCE支持:key、:from-end、:start和:end关键参数。

6.7. 哈希列表

通常使用make-hash-table创建一个空的哈希表,gethash用来获取hash表中的值,没有当前关键字对应的值则返回NIL,gethash的结果是可以直接修改的,gethash返回两个值,第一个为value第二个是布尔变量表示是否有当前键值。

(defparameter *h* (make-hash-table))

(gethash 'foo *h*) → NIL

(setf (gethash 'foo *h*) 'quux)

(gethash 'foo *h*) → QUUX

MULTIPLE-VALUE-BIND创建变量的绑定,用法与LET类似。下面的函数将value与gethash返回的键值和present与gethash返回的是否存在绑定。

(defun show-value (key hash-table)

(multiple-value-bind (value present) (gethash key hash-table)

(if present

(format nil "Value ~a actually present." value)

(format nil "Value ~a because key not found." value))))

(setf (gethash 'bar *h*) nil) ; provide an explicit value of NIL

(show-value 'foo *h*) → "Value QUUX actually present."

(show-value 'bar *h*) → "Value NIL actually present."

(show-value 'baz *h*) → "Value NIL because key not found."

可以用maphash函数来遍历哈希列表,例如下面的代码遍历并打印了整个哈希列表:

(maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) *h*)

REMHASH可以用来删除哈希表中的值:

(maphash #'(lambda (k v) (when (< v 10) (remhash k *h*))) *h*)

遍历哈希表也可以通过loop(loop的具体介绍后面在所,下面来看一下这个跟白话一样的代码见识一下):

(loop for k being the hash-keys in *h* using (hash-value v)

do (format t "~a => ~a~%" k v))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值