Lisp语言:函数的可选参数,剩余参数以及关键字参数
上一篇有关Lisp函数的文章简单介绍了函数的定义和调用,其中使用的参数是一般的参数。为了方便大家,将那篇文章的链接列出:
http://blog.csdn.net/keyboardota/article/details/7642767
如上文所述,如果定义函数时定义的是一般的参数,调用函数时传入参数的数量必须和函数定义的参数的数量相同,参数太多或者太少都会导致程序错误。
这样的函数调用起来真是有点不灵活,无法根据需要灵活传入不同数量的参数。在Lisp中解决这个问题的方法有几种,它们是可选参数,剩余参数和关键字参数,分别对应不同的使用场景。
可选参数
就如同它的名字所说明的一样,如果一个函数的一部分参数是选的,就适合使用可选参数。
比如现实使用中有一个记录人员信息的函数,要求必须提供“身份证号”和“姓名”两项内容,同时可以附加提供“身高”和体重两项,则可以把“身份证号”和“姓名”定义为必须参数,而“身高”和“体重”可以定义为可选参数。这样函数调用的时候就比较灵活,如果用户提供了“身高”和“体重”两项内容则将则两项传入函数中,如果没有提供这些信息就忽略这两个参数。
可选参数的定义使用&optional关键字,后面跟随的就是可选参数,下面是一个简单的样例:
在可选参数的使用过程中可以给参数设置缺省值,当函数调用过程中该参数没有提供时,这个参数的值会被赋予缺省值。
参数的缺省值通过一个列表的形式提供,如(c "default c" c-supplied-p),其中第一个元素c是参数名,第二个元素“default c”是缺省值,第三个元素用于判断参数是缺省值还是用户在调用时提供的。
对于第三个元素有些人会有些疑惑,为什么需要这个元素呢?不是可以通过比较判断来确定参数是否是缺省值吗?比如上面的定义就可以通过比较c和“default c”来确定参数c是否为缺省值。产生这个疑惑是因为忽略了一种情况,就是用户提供的参数恰好等于缺省值,如上面的情况中用户将c的值设置成“default c”,虽然c确实等于"default c",但是这个参数的值确实又是由调用用户提供的。这时就只能通过第三个元素来判断参数是否由调用用户提供,如果参数是有调用者提供的则第三个元素为真(T),如果调用用户没有提供这个参数则第三个元素为假(NIL)
下面是一个提供缺省值的可选参数的定义样例:
剩余参数
可选参数虽然比一般参数灵活,但是有些情况下还是不够应付实际情况,因为使用可选参数最终还是限制了参数的数量,函数调用者不能随意增加参数。
同样是录入人员信息的例子,如果需求有变化,需要函数还可以接受“肤色”和“职位”两项信息,用可选参数来实现的就需要修改。
如果有一种方法可以让函数接受无限多的参数,就可以让这个函数灵活面对不同的需求变化。这种方法就是剩余参数。在Lisp中可以通过&rest关键字为函数定义剩余参数,定义了剩余参数,函数调用过程中系统会将“剩余的参数”打包成一个列表传入函数中。所谓“剩余的参数”,就是去除必须参数后留下的其它参数。
下面是使用剩余参数的例子,函数function23中定义了a和b两个必须参数,同时定义了一个剩余参数other-parameters。调用function23过程中如果提供了超过两个的参数,第三个参数以后的所有参数会被打包成一个列表传给other-parameters.
关键字参数
使用剩余参数虽然可以接受无限多个参数,不过在使用参数的过程中严重依赖于参数的顺序。
比如人员录入的函数中将第6个参数作为人员的“职位”信息,假如函数调用者只能提供“身份证号”,“姓名”和“职位”三项信息,在函数调用过程中也需要补足6项信息。调用过程可能是这样:(function-abc "4423xxx" "张三" "" "" "" "经理"),其中有三个参数纯粹是为了凑数。
这时候如果能直接指定参数的对应关系就会比较简单,比如(function-abc 身份证号:"4423xxx" 姓名:"张三" 职位:"经理") (只是样例而已,真正语法不是这样的)
在以上这种情况下使用关键字参数就能有效解决问题。
关键字参数通过&key来定义,&key后面跟参数名。定义了关键字参数后调用函数是可以使用 “:参数名 参数值”这种形式指定参数对应关系。
下面是关键字参数定义的样例,定义了a b c d这4个关键字参数。
另外,关键字参数定义过程中也是可以指定缺省值的,指定方法和上面为可选参数定义缺省值的方法相同。具体就不再详述了,下面是样例和测试截图:
通过以上几种特殊参数的使用,函数调用者在调用函数时可以传递不同数量的参数,甚至传递不同形式的参数。如果接触过面向对象编程,对这样的概念不会陌生,因为面向对象中经常会提到“重载”的概念,它的作用也是类似的。不过需要注意的是,这里不同参数的使用和“重载”是有区别的。Clisp并不支持同一函数名的多重定义,如果同一函数名在多个地方以不同形式定义,程序不会报错,不过只有后面定义的函数才会生效。