AutoLISP基础——认识⾃定义函数
AutoLISP基础——认识⾃定义函数
(本⽂由LL_J?认识⾃定义函数?和?Autolisp编程⼼得?两篇巨著合成,并融⼊了其他⼈的⼀些经验,以快速打通你的任督⼆脉——⾃贡黄明儒注) ⼀、初识Lisp
在AutoCad命令输⼊(+ 1 2 3),回车返回6,如下
命令: (+ 1 2 3)
6
恭喜你,你已经会写Lisp程序了。这⾥,我们⽤到了⼀个系统定义的函数+,这个函数的作⽤就是对后⾯的数字求和。
上式⼀对英⽂括号组成的表达式,称之为表,Lisp语⾔也称为表的语⾔。表有两种形式,⼀种是?⼝袋式?表;⼀种是?函数式?表。前者如’(0 0)表⽰⼀个2维点,前⾯加’表⽰此表不求值。如果不加‘,则通常是认为是?函数式?表。?函数式?表如下:
①结构特点:左括号(紧跟函数,函数所需要的参数,右括号)结束。
②函数是指:系统定义的函数、⾃定义的函数、匿名函数lambda。如
((lambda(x y z) (* x y z)) 3 4 5)是合理的。
③参数可以是:数字(整数、实数)、字符串、T、nil、表(如果此
表是表、函数,就构成了复杂表,这就是lisp程序)、函数。
④表可以作为函数的参数。如(+ 1 (- 5 2) 3)中,表(- 5 2)就作为+函数
的参数。
⑤表的返回值也可以传递给其它变量,如(setq x (+ 1 2 3))
⑥接受输⼊参数,如(- 5 2)中,-函数接受5和2两个参数。
⼆、什么是函数
函数⼀词最早来源于数学,这样说:函数是将唯⼀的输出值赋予每⼀输⼊的"法则"以及该输出值与对应输⼊值的集合。
在计算机领域,对函数并没有⼀个完整的定义,百度百科有这样⼀句话:"许多程序设计语⾔中,可以将⼀段经常需要使⽤的代码封装起来,在需要使⽤时可以直接调⽤,所以,函数也可以说是许多代码的集合,这就是程序中的函数。
综合以上描述,我们可以看到"函数"的基本组成:
输⼊值:⼀般称为参数;
表达式:函数体,是代码的集合,共同组成上⾯所说的"法则";
输出值:表达式对应输⼊值的(唯⼀)结果,通常称为"返回值";
封装:不同的语⾔,封装的意义也不同,在开放式的AutoLISP 语⾔中,"封装"并不意味着编译或打包,只使之以完整的函数体驻留在内存中也应可以称为"封装" ;
可以经常调⽤:这是"函数"定义的⼀个基本要求,不能经常调⽤就不必封装成函数了。
三、AutoLISP 函数表达式
与很多的⾼级语⾔不同,函数也是AutoLISP(注:所有种类的Lisp
都是这样)的⼀种数据类型,这意味着在AutoLISP ⾥我们可以像对待其他熟悉的数据类型那样来对待函数,就像整数那样,在运⾏期创建⼀个新函数,把函数保存在变量和结构体⾥⾯,把它作为参数传给其他函数,还有把它作为函数的返回值。
函数同为数据对象,就意味着我们可以像对待其他对象那样把它传递给其他函数。这种性质对于AutoLISP 这种⾃底向上程序设计⾄关重要。AutoLISP 函数语法的描述遵循如下
这是AutoLISP 中系统⾃带函数的⼀般性语法,也包括⽤其它语⾔定义的函数。
所有AutoLISP 表达式的格式都如下所⽰:
(function arguments) ;式3-1
四、函数的定义
⼀般说来,函数通过defun 系列函数来定义,语法如下:
(defun sym argument-list expr ...) ;式4-1
①defun 函数定义⼀个名为sym的函数(defun是系统定义的⼀个函
数,其功能就是⽤来⽤户定义⾃⼰的函数)。
②函数名后⾯跟随参数列表argument-list,参数列表中对函数使⽤的
参数和局部变量进⾏声明,如果不申明任何参数和局部变量,必
须在函数名后提供⼀对空括号或者nil (注:isp语⾔中nil和⼀对空括号是等价的)。不管是否需要声明参数,但"参数列表"项是必须的,这是函数区别于赋值的⼀个重要特点。
③expr 为函数sym执⾏时的AutoLISP 表达式,可以是数字(整数、
实数)、字符串、T、nil、表、函数。见如下例4-2
④AutoLISP 定义的函数本⾝是⽆法包括"可选参数"的,函数定义
时,参数数量⼀经声明,使⽤时就⽆法增减,否则就会出现"参数太多(少)"的错误。例如:
例4-1
(defun tan(x) ;定义⼀个正切函数,函数名tan,有⼀个参数x
(/ (sin x) (cos x)) ;函数体
)
使⽤这个函数时,必须⽽且只能输⼊⼀个变量,否则出错。⽽系统⾃⼰定义的函数,参数的数量可是变的,如+。
例4-2 (defun absv(x) ;定义⼀个绝对值函数
(if (>= x 0)
x ;单⼀变量表达式
(- x)
)
)
⑤如果我们需要定义⼀个匿名函数,则可以使⽤lambda 函数,除
函数名"缺失" 外,其它都和defun 相同。匿名函数在使⽤时定
writeline输出数值变量义,普通的函数则可以在使⽤前,或任何你觉得需要的时候定
义。
⑥除此之外,给符号名赋值的set 族函数也可以⽤来定义函数,这
个问题将在后⾯会阐述。
⑦只有函数接受参数输⼊,如(setq tt '(* x 3)),tt是⼀个表,不是函
数,故(tt 3)是错误的。(setq tt +)从此以后tt代表函数+,故(tt 1
2 3)是正确的。
⑧表可以作为参数传递给其它函数,换⾔之,函数每个参数都可以
是表。每个表都返回⼀个可由外层表达式使⽤的值。如果没有外
层表达式,AutoLISP 将则该值返回给AutoCAD 命令⾏。例如,
如下代码调⽤了三个函数:
(fun1 (fun2 arguments)(fun3 arguments)) ;式1-1
第⼀个函数fun1 有两个参数,另两个函数fun2 和fun3 分别作为
fun1 的⼀个参数,本⾝各有⼀个参数。函数fun2 和fun3 被函数fun1 所包含,因此它们的返回值作为参数传递给fun1。函数fun1 由这两个参数计算函数值,并将该值返回给命令⾏。
五、函数命名规则
AutoLISP 通过符号来引⽤数据。符号名不区分⼤⼩写,可以由字母、数字和标注符号(除( ) . ' " ; 以外)的任何序列组成。符号名不能仅由数字组成。
这⾥所说的"符号"包括函数名、参数名和变量名。
需要说明的是,AutoLISP 函数名和变量名使⽤同⼀个命名空间(部分其它的LISP语⾔函数和变量使⽤不同的命名空间)也就是说,变量和函数重名时会冲突,后⾯定义(赋值)的会替代前⾯的。
可以看出,AutoLISP 的命名规则是很"宽松"的,除常见的字母数字命名外,如"abc_1、+3+、xd::abc、<3"这类看似怪异的名字都是合法的。即便如此,也有些看似合法的名字是不能使⽤的,主要有:系统的内部函数名,如princ,car 等;
系统保留的常量名或其它符号,如pi、t、nil、pause、^C 等;
系统有其它解释的符号组合,如1e2(科学计数法)+3、-2(数值)。
其它⼀些⽤于表达⽅向的字母(N、S、E、W)及表达⾓度的字母(r、g、d)等
在某些时候也会产⽣意想不到的错误,所以,在使⽤的时候也应该谨慎。因为AutoLISP 对多字节字符的⽀持不是太好,所以,虽然理论上可以使⽤汉字来作为函数名(或变量名),但实际上还是应该尽量避免的,否则可能会出现⽆法预料的错误。
⼀些特殊的表达⽅式在AutoLISP 中有特殊的含义,这些包括前缀c:和函数名s::startup,前者表⽰定义了⼀个外部函数,⽽后在则定义了⼀个⾃动执⾏函数。
我们⽤"c:3"来定义⼀个外部函数,使⽤命令"3"来执⾏,看起来这不符合规则,似乎使⽤了数字"3"来作为函数名了,但其实不然,函数名是"c:3" 。可以看出AutoLISP 中(或者说是AutoCAD 中)命令名是有独⽴空间的。即便如此,我们也不建议这样⽤,⼀般说来,函数、变量,尤其是命令的命名应该能
表达某种意义,向使⽤者和阅读者传递某种相关的信息,⽽过度简单且不能引起联想的纯数字是⽆法承载这个任务的。
从这个意义来说,函数名(或变量名)的命名每⼀个字节都应该仔
细斟酌。
对于我们这些少量编程的AutoCAD 的⽤户⽽⾔,过度复杂的函数名也会给使⽤带来不便,所以,只要有限的标识,起到便于识别的⽬的即可。
除⾮声明成局部变量,否则函数⼀经定义并加载,函数会驻留在AutoCAD 的内存中,不同程序的同名函数会相互⼲扰,造成程序不可⽤或结果错误,因此,在函数的命名时,也应考虑不同程序间函数的相互避让。
从以上意义上,笔者建议使⽤"程序名(或部分字母)+特殊字符+函数名"这种命名⽅式,如ca_main、tr:trans等。
虽然没有明确限制,但⼀些前缀在AutoCAD 中有⾃⼰的意义。⽐如"ai_"AutoCAD(随系统附带的扩展函数),"acet_" AutoCAD ExpressTools 附带的扩展函数)等,另外⼀些著名的第三⽅Lisp 扩展库,都会有⾃⼰的"专属"前缀,使⽤时也都应该注意避免。
以上所说的是⼀般意义上的函数,也可以理解为有名函数,在AutoLISP 中还有⼀种可以理解为"临时"定义的⼀次性的匿名函数(lambda 表达式)即没有名字的函数,lambda 表达式基于数学上闭包问题中的"λ演算"⽽得名,因为⽆名,所以⽆法重复调⽤,其它规则均不违背。
六、何时使⽤⾃定义函数
有重复使⽤代码的地⽅,定义函数是必要的,通常可以提⾼运⾏速度。
1.作为"模块"的⾃定义函数
程序应该有良好的结构,不仅仅是便于阅读,同时也对程序的运⾏效率有⼀定影响。多数的⾼级语⾔通过模块和转移(goto)来完成程序的结构化,和这些⾼级语⾔不同的⼀点,AutoLISP 可以通过⾃定义函数来完成这些要求,不需要编译就可以直接使⽤的代码,相⽐其他语⾔来说,更具灵活性。作为"模块"使⽤的典型的⾃定义函数主要有两类,⼀类是为对话框控件定义的动作函数,如:
例6-1
(action_tile "ttj" "(tt_tk_ttj)") ;为对话框控件ttj 指定动作函数tt_tk_ttj
另⼀类是作为"模块"使⽤的典型函数是对命令选项定义的程序分⽀函数。如:
例6-2
(setq a_ 2 se1 (ca5 -ssget "\n 拾取钢筋直径或[求和(T)]: "
"T" '((0 . "TEXT"))))
(cond
((= se1 "T") (setq b_ 1) (ca5_tot)) ;转求和分⽀函
数ca5_tot
(t ?
在程序中,为了代码的可读性,有时把因多层嵌套⽽显得"臃肿"的⼤段代码也定义成函数,从某种意义来说,这也应该归类于"模块"类,虽然这是最不像函数的函数。
作为"模块"使⽤的函数,虽然多数只使⽤⼀次,并不"经济" ,但因为这种⽅法的使⽤,使得原本凌乱的程序结构变得清晰,所以也还是值得的。
2.匿名函数
在⼀下表达循环的函数(⽐如mapcar、forearch 等)的使⽤中,我们往往见到使⽤lambda 定义的匿名函数,这些函数内容我们也可以作为⼀个"模块"定义为⼀个有名函数,然后对函数名进⾏引⽤,不过那样会占⽤更多的资源,相对⽽⾔也就不够效率了。
七、函数的作⽤机理
函数在不同情况下使⽤,其作⽤机理也是不同的。根据其和"外界"的交互作⽤的不同,笔者将函数的作⽤机理归结为"返回值作⽤"和"过程作⽤" 。
1.返回值作⽤
按函数的⼀般概念,每个函数对应输⼊值都有⼀个最终计算结果,这个最终结果也称返回值。
每个函数都有⼀个返回值,根据输⼊值(参数)的不同,返回值的结果也不同,这个不同不仅是数值的不同,也可能包括数据类型等的不同。
以返回值作为输出值的函数,在使⽤时⼀般需要对返回值进⾏"收集" 常见的,就是把返回值赋给⼀个变量,如:
(setq y (mapcar ?)) ;式7-1,把函数mapcar 的返回值赋给变量
y
在定义⼀个返回值作⽤的函数时,应该保证在函数的最后得到的结果是需要的返回值结果,⽐如:
例7-1
(defun add(a b c) ;返回a、b、c 的求和结果
(+ a b c) ;对a、b、c 求值,同时返回最后结果
)
在函数内容稍复杂时,有时最后的结果可能并不是我们需要的返回值,此时我们可以在函数的最后加上⼀句确定可以表⽰需要的返回值的代码(或变量名),以保证最后的输出是我们需要的结果。
⼀般的涉及数据处理的函数多是返回值作⽤函数。
匿名函数⼏乎都是返回值作⽤函数。
2.过程作⽤
在系统⾃带的函数说明中,⼏乎都对返回值进⾏了详细的交待,但有些函数即使有返回值,我们仍旧只是关⼼他们的执⾏过程,⽐如应⽤管理类函数如(load 函数等)及⼀些⽂件处理类函数(如write-line 等)在常⽤的函数中,foreach 函数和mapcar ,都是对表中的每个元素进⾏操作的,但他们有⼀个很⼤的不同,就是我们更关⼼mapcar 函数的返回值,⽽对于foreach 函数,我们⼏乎都只是关⼼其执⾏过程。每个函数都是对应于输⼊值来求"唯⼀的"输出值,这⾥的"唯⼀"应该解释成"可控制的" 即对应于不同的输⼊值,函数的运⾏过程和最终结果都是可控制的⽽⼀个函数可能并不仅仅通过最终的结果(返回值)和外界进⾏交互,在运⾏过程中,也可以有更多的交互发⽣,这就是过程作⽤,记住,⽆论如何,这种交互应该是可控制的。
系统⾃带函数command 通过调⽤系统命令来对系统进⾏操作,是典型的过程作⽤函数,并且,AutoLISP 设定,函数command 的返回值总是nil。
3.返回值作⽤和过程作⽤的关系
对于可以重复使⽤的多数函数和多数的对话框控件动作函数⽽⾔,返回值是必须的,程序在函数的使⽤过程中,并不"关⼼"函数的执⾏过程,⽽是"专⼼地俘获" 函数的最终结果,将最终的俘获结果作为⼀种数据形式进⾏使⽤。⽽对于作为"分⽀"或"减肥"的另⼀些⾃定义函数⽽⾔,函数只是把⼀部分代码换⼀个地⽅书写,所以除了最终的返回值,⼤家更多地是关⼼这些函数的运⾏过程。这就是返回值作
⽤和过程作⽤的区别,换⼀句话可以这样说,通过返回值作⽤的函数是封闭的,⽽通过过程作⽤的函数是开敞的。
因为作⽤机理不同,我们定义函数时,也应该注意他们各⾃的特
点,尤其对于返回值作⽤的函数,我们应该尽可能地保证其执⾏过程的"封闭性" 避免对外部"环,境"造成影响(在⼀些Lisp ⽂献中,把这种影响称之为"副作⽤"。)
举个例⼦说明返回值作⽤和过程作⽤的影响:
例7-2
(defun c:tt()
(princ 3) ;在命令⾏打印数字3
)
命令: tt ;执⾏函数
33 ;前⾯的3 是过程作⽤,后⾯的3 是返回值
在这⾥我们看到了返回值对命令⾏打印的输出显⽰的影响,这种影响是⽆碍的,可以通过在函数结束前增加⼀句"(princ)"来消除,但有时,⼀些相互影响是致命的(⽐如不⼩⼼修改了全局变量),就需要特别注意了。
⼋、变量

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。