唯她
频道主
lisp 基础知识
基本概念
符号
符号(Symbols)就是一串字符。你可以在符号中包含字母、数字、连接符等等。
这些组成符号的单位不能有 “(双引号) 、’(单引号) 。” 是用于表示字符串的, ’ 是用 lisp 中 操作符 quote 的简写。
如果你只输入数字,LISP会认为你输入了一个整数而不是符号。
下面是符号的一些范例:
===========================
a
b
c1
foo
bar
baaz-quux-garply
===========================
符号不分大小写 , abc 和 Abc 、 ABC .被认为是同一个符号。
如果你学过 C 语言,可以认为 符号 是指针,指向了一个内个地址,而这个地址存储了这个符号的 值。 也可以认为 符号 是变量,变量的值可以是 整数、实数、字符串,文件指针。也可以CAD中的其它 类型,如图元,选择集,对象等(这个以后会详细说明,不用深究)。
即然符号是指针(变量),那么它必然会有一个对应的值。我们需要对符号进行赋值操作 , 在lisp 中 赋值操作符是 setq 。
在 C 语言中 我们用 = 号赋值
int a = 5;
b = "str";
在 lisp 中 ,我们用 setq 赋值 , 下面示例中的每行,都在 CAD 命令行试一下, 不用输入 ; 及后面的内容 。 ; 及后面的只是为了说明这一行的作用。
(setq 符号 值)
(setq 符号 值 符号 值 符号 值 符号 值 符号 值 ...)
;; 示例
(setq a 5) ; 把数值 5 存入 a 这个符号里面。
(setq a 8 b "str")
特殊的符号
有两个比较特别的符号就是 t 跟 nil 。
t 这个符号所定义的值就是 t ,而 nil 这个符号所定义的值就是 nil 。
Lisp 把 t 跟 nil 这两个值拿来表示“真”与“假”。一个最典型会用的 t 跟 nil 的例子就是 if 函数,将会更清楚的解释介绍 if 函数。
> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5
最后一个例子或许会让你感到很奇怪,不过它并没有错误。原因是 nil 表示“假”, 而任何其它的值都表示“真” 。(除非你有理由要这样写程序,不然通常我们还是习惯用 t 来表示“真”,这样读程序的时候也比较清楚。)
像 t 和 nil 这样的符号被称为自解析符号,因为他们解析为自身。
数值
整数的定义就是一连串的数字,并且最前面可以选择性的加上+ 或 - 。
而实数包含有整数,而且比整数定义广的是,实数还可以有小数点,也可以用科学记号表示。
下面是一些数值类型的例子:
5
17
-34
+6
3.1415
1.722e-15
对于数值可以进行运算,一些常见的数值函数如 +, -, *, /, floor,ceiling, mod, sin, cos, tan, sqrt, exp, expt 都有内建,而且这些内建的数值函数可以接受任何数值类型的参数。
+, -, *, / 这四个函数的返回值类型会随输入参数的类型而自动延伸为较广的类型范围,比如说整数加上有理数的返回值,就会是范围较广的有理数;而有理数加上实数的返回值,是实数;实数加上复数的返回值,则是复数。下面是一些例子:
> (+ 3 5.3) ;返回值类型范围自动加广
8.3
> (exp 1) ;自然对数的基底 e
2.7182817
> (exp 3) ;e*e*e
20.085537
> (expt 3 4.2) ;指数函数,以 3 为底数,次方数是 4.2
100.90418
> (+ 5 6 7 (* 8 9 10)) ;内建的 +,-,*,/ 四个算数函数都可以接受多个参数值的调用
数值范围,因计算机存储数值用的内存一般是 双精度数。(学过 C 语言的应该知道,不知道也没关系。记住表示的数是有范围的就可以了。)
点对 cons car cdr
图中 方框表示点对;圆表示值,也就是地址上存放的数值,字符串,函数等。
(setq a 5 b 6)
(cons a b)
(setq a 5)
(cons a nil)
(setq a 5 b (cons 6 nil))
(cons a b)
点对 (cons) 就是一个有两个部分组成的数据单元。由于一些历史上的因素,这两个部分分别称作 “car” 跟 “cdr” 。
也可理解为 点对 是 一个阴阳球,由 阴 (前面)和阳(后面)两部分组成。 car 取出 阴, cdr 取出阳。
car cdr 取出的 阴 或 阳 ,也是因 阴阳球组成的,可以一直分下去。直到不可分为止。 这个不可分,在lisp 中叫 原子 (atom) .
这个数据单元是组成表的基本单位。
(在第一台实作 Lisp 语言的机器上, CAR 与 CDR 指令分别表示”Contents of Address Register” 及”Contents of Decrement Register”。而 cons 就是透过这两个缓存器而实作的。) Cons 很容易使用:
> (cons 4 5) ; 设置一个 cons ,其中 car 设为数字 4 ,而 cdr 设为数字 5 。
(4 . 5)
> (cons (cons 4 5) 6) ; 设置一个 cons ,其中 car 设为一个点对(4 . 5),而 cdr 设为数字 5 。
((4 . 5) . 6)
> (car (cons 4 5)) ; 取出 (4 . 5) 的 car 设定值。
4
> (cdr (cons 4 5)) ; 取出 (4 . 5) 的 cdr 设定值。
5
链表 list
利用点对(Cons)我们可以创造出很多结构,而当中最简单的,或许就是链表(linked list)。链表其实就是把 Cons 的 CAR 指定成某些元素,而把 CDR 指定到另一个 Cons 或是 NIL 。如下,我们可以经由 list 函数来创造链表。
> (list 4 5 6)
(4 5 6)
看到上面的例子,你应该有注意到 Lisp 在打印链表的时候,会有一些原则:它输出的时候会省略掉一些 . 连结点对的点,以及 () 括号。而省略的原则如下,如果这个点对的 CDR 是 NIL 的话,那这个 NIL 跟它前面的连结点将不会被印出来;如果这个点对 A 的 CDR 是另外一个点对 B 的话,那在点对 B 前面的连结点以及点对 B 本身的小括号都不会被印出来。如下例子:
> (cons 4 nil)
(4) ;; 完整表示为(4 . nil)
> (cons 4 (cons 5 6))
(4 5 . 6) ;; 完整表示为(4 . (5 . 6))
> (cons 4 (cons 5 (cons 6 nil)))
(4 5 6) ;; 完整表示为 (4 . (5 . (6 . nil)))
最后的这个例子,其实跟直接调用函数 (list 4 5 6) 是等价的。
注意 NIL 在这儿的含义就是没有包含任何元素的链表。比如说,包含两个元素的链表(a b)中,cdr是(b),一个含有单个元素的链表;包含一个元素的链表(b),cdr是nil,故此这里必然是一个没有元素的链表。
nil 的 car 跟 cdr 都定义成 nil。 (从 空 里 取出的依然是 空)
函数 defun
在 c 语言中 有函数的概念,而 lisp 中 基本上都是函数。
(defun 函数名 (参数1 参数2 ... 参数n)
;; 函数实现代码,由一系列的表过式组成
...
...
)
> (+ 3 4 5 6) ; 加法函数可以接受任意多的输入参数
18
> (+ (+ 3 4) (+ (+ 4 5) 6)) ; 或是你也可以像这样加,哈~
22
> (defun foo (x y) (+ x y 5)) ; 定义一个叫做 foo 的函数
FOO
> (foo 5 0) ; 调用函数,传入的参数个别是 5 跟 0
10
> (defun fact (x) ; 以递归调用的方式定义函数 fact
(if (> x 0)
(* x (fact (- x 1)))
1))
FACT
> (fact 5)
120
> (defun a (x) (if (= x 0) t (b (- x)))) ; 以两个函数相互调用的递归方式来定义函数
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T
> (defun bar (x) ; 一个函数的定义里面如果有很多语句句的话
(setq x (* x 3)) ; 那整个函数的返回值,
(setq x (/ x 2)) ; 将会是最后的一个语句句
(+ x 4))
BAR
> (bar 6)
13
函数 foo 的表达式 链表 表示图
(defun foo (x y) (+ x y 5))
上面第一个定义 defun 例子中,我们在定义 foo 函数的时候,要求要两个传入值 x 及 y 。所以每当要调用 foo 时,都要恰好给它两个传入值,第一个传入值将会变成在 foo 函数里面的 x 变量值,而第二个传入值将会变成在 foo 函数里面的 y 变量值。
显示 print princ
显示(Printing)。有些函数会导致输出,而最简单的输出,就是透过调用 print 函数,它会把参数给输出到屏幕上,然后函数的返回值也是刚刚输出的结果。
> (print 3)
3
3
第一个 3 是因为调用 print 函数而把参数输出到屏幕上,第二个 3 则是调用函数之后的返回值。
表达式
表达式相当于 C 语言中的一条条语句
一个最简单的 Lisp 表达式是整数。如果我们在提示符后面输入 1 ,
> 1
1
系统会打印出它的值,接着打印出另一个提示符,告诉你它在等待更多的输入。
在这个情况里,打印的值与输入的值相同。数字 1 称之为对自身求值。
当我们输入需要做某些计算来求值的表达式时,生活变得更加有趣了。举例来说,如果我们想把两个数相加,我们输入像是:
> (+ 2 3)
5
在表达式 (+ 2 3) 里, + 称为操作符,而数字 2 跟 3 称为实参。
在日常生活中,我们会把表达式写作 2 + 3 ,但在 Lisp 里,我们把 + 操作符写在前面,接著写实参,再把整个表达式用一对括号包起来: (+ 2 3) 。
这称为 前序表达式 。一开始可能觉得这样写表达式有点怪,但事实上这种表示法是 Lisp 最美妙的东西之一。
举例来说,我们想把三个数加起来,用日常生活的表示法,要写两次 + 号,
2 + 3 + 4
而在 Lisp 里,只需要增加一个实参:
(+ 2 3 4)
日常生活中用 + 时,它必须有两个实参,一个在左,一个在右。前序表示法的灵活性代表著,在 Lisp 里, + 可以接受任意数量的实参,包含了没有实参:
> (+)
0
> (+ 2)
2
> (+ 2 3)
5
> (+ 2 3 4)
9
> (+ 2 3 4 5)
14
由于操作符可接受不定数量的实参,我们需要用括号来标明表达式的开始与结束。
表达式可以嵌套。即表达式里的实参,可以是另一个复杂的表达式:
> (/ (- 7 1) (- 4 2))
3
上面的表达式用中文来说是, (七减一) 除以 (四减二) 。
Lisp 表示法另一个美丽的地方是:它就是如此简单。所有的 Lisp 表达式,要么是 1 这样的数, 原子,要么是包在括号里,由零个或多个表达式所构成的列表。以下是合法的 Lisp 表达式:
2
(+ 2 3)
(+ 2 3 4)
(/ (- 7 1) (- 4 2))
稍后我们将理解到,所有的 Lisp 程序都采用这种形式。而像是 C 这种语言,有着更复杂的语法:算术表达式采用中序表示法;函数调用采用某种前序表示法,实参用逗号隔开;表达式用分号隔开;而一段程序用大括号隔开。
在 Lisp 里,我们用单一的表示法,来表达所有的概念。
在 lisp 中,表达式所表示一切,定义函数是写表达式,各种操作都是写表达式,编程,开发都是在写表达式。
lisp 运行
求值 eval
当一段表达式被 lisp 解释器读入的时候,解释器就会对这段表达式求值,求值就相当于运行程序。
就像当小学生看见一个 数学公式 时,就要对它进行计算一样。
所以 lisp 程序的运行过程,就是这些表达式集合的求值过程.
表达式能被求值的前提条件是 表达式列表的第一个符号必须是函数 如下
(函数 参数 参数 (函数 参数 (函数 .....)))
紧跟 括号的第一个符号必须是函数。
quote
有一个办法可以阻止表达式被求值,那就是 quote 函数(操作符)。quote 可以简记为 ’ 。
有了 quote 和 eval 就可以控制 表达式求值 的过程和节拍。
(+ 1 2) ;; 我们输入一个表达式,结果是 3 ,也就是对这个表达式求值了。
(quote (+ 1 2)) ;; 返回结果 仍然是 (+ 1 2) 也就是保持原样。quote 阻止了求值。
(setq a 5) ;; 给符号 a 赋值 为 5
(quote a) ;; 返回符号 a ,而不是 5
(+ 1 a) ;; 返回 6 .
progn
表达式集合,当要执行(求值) 多个表达式,但要求作为输入用于 其它函数的参数时,可以用 progn 作为一个整个块,包裹多个表达式。
我们也可以用 (if t exprs) 代替 progn 。
分支 cond if
分支给程序加上了判断的功能。
当有满足的条件后,执行内部的表达式,不再对后面的条件进行判断。
(cond
(条件1
执行满足 条件1 时的表达式)
(条件2
执行满足 条件2 时的表达式)
(条件3
执行满足 条件3 时的表达式)
(条件n
执行满足 条件n 时的表达式)
(t
以上没有满足条件时的表达式))
(if 条件
(progn
条件为真时执行的表达式)
(progn
条件为假时执行的表达式))
注: 当满足左边的条件时,不再判断和执行后面的条件;
循环 while foreach repeat
(while condition ;; 当 condition 为 t 时,执行 exprs
exprs ;; exprs 中应该有改变 condition 的值,当 condition 为 nil 时 即出循环。
)
(foreach each lst ;; 从表 lst 中依次取出 元素作为 each 的值 , 执行 expr
exprs)
(repeat n ;; 执行 n 次 exprs
exprs)
- 下载图片
- 复制图片
2023-05-16
浏览96
开发相关
登录后评论
1
评论
分享