Common-Lisp常用函数速查速记笔记

Common-Lisp常用函数速查速记笔记

这是一本Common-lisp常用函数和宏查询手册,将持续更新。当然如果你有英文阅读能力,我还是建议你手中常备一本CookBook。

cookbook

列表操作

list 生成列表

生成列表。

1
2
(list 'a 'b)
; (A B)

cons 构造列表

把两个列表构造成一个列表。

1
2
3
4
5
6
7
8
(cons 'a '(b c d))
; (A B C D)
(cons 'a (cons 'b nil))
; (A B)
(cons 'a 'b)
; (A.B)

car 获取列表中的第一个元素

1
2
(car '(a b c))
;A

cdr 获取第一元素后面的所有元素

1
2
(cdr '(a b c))
; (B C)

copy-list

建立一个列表的拷贝。注意是新建cons而不是之前原来的列表对象。

1
2
3
(setf x '(a b c)
y (copy-list x))
;(A B C)

make-list

关键字参数来构建一个任意长度的元素的列表。

1
2
(make-list 3 :initial-element 0)
;(0 0 0)

nth 获取列表中的car指针(0索引)

获取列表中某个索引的元素。

1
2
(nth 0 '(a b c))
;A

nthcdr 获取列表中的cdr指针(0索引)

获取列表中某个元素后面的其他元素构成的列表。

1
2
(nthcdr 1 '(a b c))
;(C)

subseq 截取列表

根据开始和结束索引截取列表的子列表。

1
2
3
4
(subseq '(a b c d) 1 2)
;(B)
(subseq '(a b c d) 1)
;(B C D)

reverse 反转列表

1
2
(reverse '(a b c))
;(C B A)

sort 排序

适应自定义的函数对列表进行排序。注意这个是函数是产生副作用的。所以为了保护数据,可以使用(copy-list)对原来的数据进行保护

1
2
(sort '(0 1 3 2 55 4) #'>)
;(55 4 3 2 1 0)

remove 不含元素的新列表

举例来说,函数 remove 接受一个对象和一个列表,返回不含这个对象的新列表
注意原理的列表并没有发生任何的改变

1
2
3
(setf lst '(a c a r t))
(remove 'a lst)
lst

逻辑操作

if

(if [逻辑表达式] [表达式1] [表达式2]) 逻辑表达式为真 求值表达式1 否则求值表达式2

1
2
(if (listp ‘(a b c)) (+ 1 1) (- 1 1))
; 2

其中listp 是判断一个元素是不是列表。

and

输入一组表达式。如果所有都为真,那么返回最后一个表达式的值。否则返回nil;

1
2
3
4
(and t t t (- 1 1) (+ 1 1))
; 2
(and nil t t t (+ 1 0))
; nil

or

值返回第一个值位真的表达式的值。

1
2
(or (+ 1 1) 'no)
; 2

循环操作

do (do-while循环)

循环语句 (do (参数列表) (退出条件 退出返回) (循环求值表达式));
参数列表(变量名 初始化值 变量更新方式)

1
2
3
4
5
6
7
8
9
10
11
12
(defun test (x)
(do ((i 0 (+ i 1)))
((> i x) 'done)
(fromat t "~A~%" i)))
(test 3)
;0
;1
;2
;3
;DONE

dolist

循环一个列表(用法类似其他语言中的foreach)

1
2
3
4
5
6
7
8
9
(defun my-list-length (lst)
(let ((len 0))
(dolist (obj lst)
(setf len (+ len 1)))
len))
(my-list-length '(a b c))
;3

函数

apply

调用一个函数求值并返回。参数作为列表传入。

1
2
(apply #'+ '(1 2 3 4 5))
;15

funcall

调用一个函数求值并返回。参数不需要使用列表包裹。

1
2
(funcall #'+ 1 2 3)
;6

lambda

用于生成一个匿名函数。相当于返回了函数名。

1
2
(funcall #'(lambda (x) (+ x 100)) 1)
;101

mapcar (映射函数)

把列表中每个个car指针送到函数中求值,然后返回值构成新的列表。

1
2
3
4
5
6
7
8
(mapcar #'(lambda (x) (+ x 10))
'(1 2 3))
;(11 12 13)
(mapcar #'list
'(a b c)
'(1 2 3 4))
;((A 1) (B 2) (C 3))

maplist

把列表中每个个car指针送到函数中求值,然后返回值构成新的列表。

1
2
(maplist #'(lambda (x) x)
'(a b c))

其他

progn

对一组表达式,依次求值,并且返回最后一个表达式的值。

集合操作

member 返回符合条件的列表(集合)中的元素

返回一个列表中符合要求的元素之后的列表。其中条件可以进行自定义。也可以对列表中元素进行自定义。

1
2
(member 2 '((1) (2)) :key #'car :test #'equal)
;((2))

member-if 返回任意一个要求函数的元素后面的列表

其中条件是一个函数这个函数是自定义的。

1
2
(member-if #'oddp ‘(2 3 4))
;(3 4)

adjoin

接受一个元素和一个列表。如果这个元素不存在在列表中在加入到列表中。

1
2
(adjoin 'a '(b c))
;(A B C)

union 并集

intersection 交集

set-difference 补集

push 压栈

pop 出栈

pushnew 只进新元素的压栈

数学

oddp

判断一个数字是不是奇数。是返回T 否则返回NIL

输入输出

read 输入(PS:这里更像是等待输入)

记住 read 会一直永远等在这里,直到你输入了某些东西,并且(通常要)按下回车。因此,不打印明确的提示信息是很不明智的,程序会给人已经死机的印象,但其实它是在等待输入。
第二件关于 read 所需要知道的事是,它很强大: read 是一个完整的 Lisp 解析器(parser)。不仅是可以读入字符,然后当作字符串返回它们。它解析它所读入的东西,并返回产生出来的 Lisp 对象

format 输出

1
2
3
(format t "~A is a ~A ~% !!" 1 (+ 1 1)
'DONE)
;;

其中 ~A是占位符 ~%是换行符。最会返回NIL而不是T。NIL
不代表着打印失败了。打印是程序的副作用和取值无关。

变量

let 局部变量

PS. 我敢打包票js中的关键字是灵感来源于此。

1
2
3
(let ((x 1) (y 2)))
(+ x y))
;;3

defparameter 全局变量

1
(defparameter *glob* 99)

defconstant 全局常量

1
(defconstant limit (+ *glob* 1))

boundp 检查一个符号是否是全局的变量或者常量

1
(boundp '*glob*)

typep 变量类型判断

特殊的数据结构

数组操作

make-array 创建数组

1
(setf arr (make-array '(2 3) :initial-element nil));

上面的代码可以创建一个2X3的数组。其中使用了关键字参数。表示初始化所有的值都是nil

aref 取出数组中的位置的值(0索引的)

1
2
3
4
(aref arr 0 0)
; NIL
(setf (aref arr 0 0) 'b)
;B

这里就可以把数组里(0,0)的位置赋值为B了,但是注意这里是赋值成了符号’B,不是字符串“B”哦。

表示字面常量的数组

一般来说使用#na的语法来表,比如:
#2a((a b c) (nil nil nil))
如果全局变量 *print-array* 为真,则数组会展示成这种形式。

vector 和 svref 向量操作

一维的数组表示为向量,生成时直接传参数就可以了。读取和数组是一样的0索引。

字符串

字符串就是字符构成的数组

char-code code-char 字符串的ASCII转换

char

取字符串的第n个字符

字符串的比较

  • sring-equal 不区分大写写的比较
  • equal 区分大小写的比较

结构体

defstruct 定义结构体

1
2
3
(defstruct point
x
y)

这个方法是定义了一个结构体。同时隐式的定义了一些方法:

  • make-point
  • point-p
  • copy-point
  • point-x
  • point-y

输入输出流

路径名 make-pathname

路径名(pathname)是一种指定一个文件的可移植方式。路径名包含了六个部分:host、device、directory、name、type 及 version。你可以通过调用 make-pathname 搭配一个或多个对应的关键字参数来产生一个路径。在最简单的情况下,你可以只指明名字,让其他的部分留为缺省:

1
2
(setf path (make-pathname :name "my.file"))
; #P"my.file"

open 打开一个文件流

open可以接受一个上面那样的标准路径名数据,之后打开一个文件流。在创建这流的时候,可以使用
关键字参数来指定流的行为。比如读写或者是读写都做。还可以指定在缺省条件下的行为。
比如我们现在建立一个新建文件的输出流。他是这样的。

1
2
3
4
5
6
7
8
9
;;创建一个路径到myfile.md
(setf filepath (make-path :name "myfile.md"))
;;创建输出流 :supersede 表示取代之在文件存在的情况下
(setf mysteam (open filepath :direction :output
:if-exists :supersede))
;;现在我们用之前format函数随便写点什么吧!
(format mysteam "Holle dream! ~%")
;;记得关闭流 不然现在查看文件很可能是空的
(close mysteam)

read-line 读取流中的一行

少废话上代码:

1
2
3
4
5
6
7
(setf path (make-path :name "myfile"))
(setf stream (open path :direction :input))
(read-line stream)
;;打印出来文件中的第一行
(close stream);;关闭流养成好习惯

with-open-file (打开文件宏)

打开操作一起哈成

1
2
3
(with-open-file (str path :direction :output
:if-exists :supersede)
(format str "Something~%"))

包定义

1
2
3
4
5
6
(defpackage "MY-APPLICATION"
(:use "COMMON-LISP" "MY-UTILITIES")
(:nicknames "APP")
(:export "WIN" "LOSE" "DRAW"))
(in-package my-application)

这里主要是在export是你要 分享出去的包。

类型转化

float 将任何实数转换成浮点型

1
2
(mapcar #'float '(1 2/3 .5))
;;(1.0 0.6666667 0.5)

truncate 返回任何实数的整数部分

1
2
3
(truncate 1.3)
;1
;0.2999995

floor 向下取整

ceiling 向上取整

round 四舍五入

mod 取余数

signum 符号函数

abs 绝对值函数

random 随机数

expt 指数

1
(expt x n)

宏相关

defmacro 宏定义

下面有具体的例子

` 和 , 和 ,@

少废话看代码。

1
2
3
4
5
6
(setf lst '(a b c))
;(A B C)
`(a is ,lst)
;(A IS (A B C))
`(b is ,@lst)
;(B IS A B C)

那么这样的好处是什么?看看我们自己定义一个while函数。

1
2
3
4
(defmacro while (test &rest body)
`(do ()
(not ,test)
,@body))

gensym 返回一个独一无二的符号

在宏的定义的时候为什么要使用这个函数呢?
那是因为在使用内部的变量的时候会出现,外面的定义个宏内部定义一样导致变量被入侵的情况。
于是在写宏的时候就要考虑得和程序设计人员一样。
除此之外还有就是 传入的表达式很肯能导致变量被多重取值了。这是很恐怖的。

这里有一个把一段代码取值n次的宏定义。注意体会他的脊髓。

1
2
3
4
5
6
7
(defmacro ntimes (n &rest body)
(let ((g (gemsym))
(h (gemsym)))
`(let (,h ,n))
(do ((,g 0 (+ ,g 1)))
((>= ,g ,h))
,@body)))

注意这没有反引号的生成的新符号保证了在,反引号里面的h并不在被重新求值了。
这个是很重要的。在宏的设计里面。

给作者买杯咖啡吧。喵~