第五章:函数
所有的 lisp 程序的最基本的组成:函数、变量、宏。
函数用宏defun
定义
格式:(defun 函数名 (形参列表) 函数体)
如:(defun say (str) (format t "~A" str))
约定:
- 一般类型转换的函数会在名字中使用 →
- 将一个字符串转为微件(widget)的函数会叫做 string->widget
- 函数名中的连接符不是下划线,而是横线。
在函数中紧跟这个形参列表之后的字符串可作为描述函数用途的文档字符串。
可通过函数documentation
获取。
函数将最后一个表达式的值作为整个函数的返回值。也可用 return-from 在函数任何位置立即返回。
函数形参列表
必要参数:不用关键字修饰的形参,一般为必要参数。当函数调用时,必须为其提供一个实参,每个形参绑定对应的实参。若实参个数过多或过少,则会报错。
可选参数:形参前用关键字&optional
修饰。在所有必要形参都被赋值之后,若还有剩余实参,则被赋给可选形参。若未被赋值的可选形参会自动绑定值 nil。可设置默认值,用列表表示。如:(b 10)。若可选形参未传值,则使用默认值。若检测是否使用默认值,可用形参名加-supplied-p 后缀来判断。如:(b 10 b-supplied-p)。当使用默认值为 nil,不使用默认值为 T
函数形参列表
必要参数:不用关键字修饰的形参,一般为必要参数。当函数调用时,必须为其提供一个实参,每个形参绑定对应的实参。若实参个数过多或过少,则会报错。
可选参数:形参前用关键字&optional 修饰。在所有必要形参都被赋值之后,若还有剩余实参,则被赋给可选形参。若未被赋值的可选形参会自动绑定值 nil。可设置默认值,用列表表示。如:(b 10)。若可选形参未传值,则使用默认值。若检测是否使用默认值,可用形参名加-supplied-p 后缀来判断。如:(b 10 b-supplied-p)。当使用默认值为 nil,不使用默认值为 T 例子:
(defun test (a &optional (b 10 b-supplied-p)) (format t "a: ~A b: ~A flag: ~A" a b b-supplied-p))
测试代码:
CL-USER> (test 2) a: 2 b: 10 flag: NIL NIL CL-USER> (test 2 4) a: 2 b: 4 flag: T NIL
- 剩余参数:形参前用关键字&rest 修饰。若当实参个数满足必要形参和可选形参时,剩余实参会被放进一个列表作为剩余形参的值。
例子:
(defun test (a &optional b &rest values) (format t "a: ~A b: ~A values: ~A" a b values))
测试代码:
CL-USER> (test 1 2 3 4 5 6) a: 1 b: 2 values: (3 4 5 6) NIL
- 关键字参数:形参前用关键字&key 修饰。在函数调用时可用:形参名(即关键字)来给特定的形参赋值。此形参也可设置默认值及判断是否使用默认值。用法和可选参数相同。还可以通过列表设置关键字别名,在函数调用时,只能通过别名来赋值。如:((:app a))
例子:
(defun test (a &key ((:bp b)) ((:cp c) 3 c-supplied-p) ) (format t "a: ~A b: ~A c: ~A c-flag: ~A" a b c c-supplied-p))
测试代码:
CL-USER> (test 1 :bp 2) a: 1 b: 2 c: 3 c-flag: NIL NIL CL-USER> (test 1 :cp 4) a: 1 b: NIL c: 4 c-flag: T NIL CL-USER> (test 1 :cp 3) a: 1 b: NIL c: 3 c-flag: T NIL
混合使用不同形参类型
混合使用不同形参类型时的声明顺序:必要参数、可选参数、剩余参数、关键字参数。 一般组合使用的情况:必要参数和其他的一种类型组合使用。
关键字参数和剩余参数、可选参数组合使用时会出现奇怪的行为,应避免一起使用。(若未给可选参数提供值,则会将关键字参数的关键字和值作为可选参数。)
也有可以组合使用的例子,但暂时先不考虑。
可选参数仅适用于一些较为分散且不确定调用者会提供值的形参。 剩余参数适用于接收可变数量的实参。 关键字参数适用于给指定参数赋值。
函数返回值
函数默认会将最后一个表达式的值作为整个函数的返回值。
可使用return-from
使函数在特定位置返回。第一个参数为在返回函数中的函数名,第二个参数为返回值。
作为数据的函数--高阶函数
一般使用函数名来调用函数,若将函数看成数据则可将函数作为参数传给另一个函数。
在 lisp 中,函数是另一种类型的对象。
用 defun 定义一个函数时,创建一个新的函数对象及赋予其一个名字。
使用特殊操作符 function 可获取一个函数的函数对象。接收一个参数并返回参数同名的函数对象。
functiong 的语法糖为:#'
通过函数对象调用函数的两个函数:funcall
和apply
funcall:用于知道传递给函数的实参个数。第一个参数为:被调用的函数对象,其余的参数为传入函数的参数。
apply:第一个参数是被调用的函数对象。第二个参数是一个列表,将传入被调函数的参数放到一个列表中。
funcall 和 apply 的区别:funcall 应用到被调函数上的参数为单一参数,而 apply 则将一个列表作为应用参数。
例子:
(defun test-fun (fn)
(funcall fn 1 2 3))
(defun test-app (fn)
(apply fn '(1 2 3)))
测试代码:
CL-USER> (test-fun #'+)
6
CL-USER> (test-app #'+)
6
匿名函数
使用 lambda 表达式可创建一个匿名函数。第一个参数是形参列表,第二个参数是函数体
格式:(lambda (形参列表) 函数体)
lambda 表达式重要用途是制作闭包,即捕捉了其创建时环境信息的函数。
例子:
(defun test (x y)
(lambda (x y) (+ x y)) x y)
测试代码:
CL-USER> (test 1 2)
2