1、第1章,AutoLISP和Visual LISP,1.1 关于LISP,LISP是人工智能领域中广泛采用的一种程序设计语言。主要用于人工智,能、机器人、专家系统、博奕、定理证明等领域。,LISP也被称为符号式语言,因为它处理的对象是符号表达式。LISP语言,的程序和数据都是以符号表达式的形式来表示,因此,一个LISP程序可以把,另一个LISP程序作为它的数据来处理。,LISP语言语法简单,编写程序便捷、灵活,数据类型丰富。利用LISP语,言可以很容易地定义或调用新的函数。这就是Autodesk公司早在1985年的2.1,版本就引用了LISP语言的宏操作,在1986年的2.5版本就推出了比较完整
2、的,AutoLISP语言的原因。,LISP在它几十年的发展过程中产生了多种版本,如MacLISP、InterLISP,、ZetaLISP、和CommonLISP等。其中CommonLISP是近几年美国的几所大,学(如麻省理工、斯坦福等)和工业界(如Bell实验室、DEC公司、HP公司,等)的人工智能研究人员协同推出的,它概括了MacLISP、InterLISP、,ZetaLISP等版本的特点,因而功能较强且拥有其它版本的一些优点,是目前,LISP语言较完整的版本。,1.2 关于AutoLISP,AutoLISP是为二次开发AutoCAD而专门设计的编程语言,它起源于LISP,语言,嵌入在Aut
3、oCAD的内部,是LISP语言和AutoCAD有机结合的产物。,AutoLISP 采 用 了 和 CommonLISP 最 相 近 的 语 法 和 习 惯 约 定 , 具 有,CommonLISP的特性,但又针对AutoCAD增加了许多功能。它既有LISP语言,人工智能的特性,又具有AutoCAD强大的图形编辑功能的特点。它可以把,AutoLISP程序和AutoCAD的绘图命令透明地结合起来,使设计和绘图完全融,为一体,还可以实现对AutoCAD图形数据库的直接访问和修改。,利用AutoLISP语言可以进行各种工程分析计算、自动绘制复杂的图形,,还可以定义新的AutoCAD命令、驱动对话框、控
4、制菜单。为AutoCAD扩充具,有一定智能化、参数化的功能,可以使设计人员的主要精力用于产品的构思,和创新设计上,实现真正意义上的计算机辅助设计。,1.3 关于Visual LISP,Visual LISP是Autodesk公司在1997年14版本推出的。它是为,加速AutoLISP程序开发而设计的软件开发工具,是一个完整的,集成开发环境。 Visual LISP包括文本编辑器、格式编排器、语,法检查器、源代码调试器、检验和监视工具、文件编译器、工,程管理系统、上下文相关帮助与自动匹配功能和智能化控制台,等。,Visual LISP用户界面良好,用过Microsoft软件的用户只需很,短的时间
5、即可掌握它。,Visual LISP兼容以前的AutoLISP程序。在Visual LISP集成环,境下开发AutoLISP程序,就不再象以前那样编辑程序时,用其,它系统的文本编辑程序编写程序代码;调试程序时,需要用户,自己决定在程序的什么位置插入打印语句,以便查看变量的内,容;在程序运行正常后,再将插入的调试代码删除或注释掉。,在VLISP集成环境下可以便捷、高效地开发AutoLISP程序,,可以经过编译得到运行效率更高、代码更加紧凑、源代码受到,保护的应用程序。,Visual LISP是新一代AutoLISP语言,它对AutoLISP语言进行,了 扩 展 , 可 以 通 过 Microso
6、ft ActiveX Automation 接 口 与,AutoCAD对象交互,可以通过反应器函数扩展AutoLISP响应事,件的能力。,如果使用Visual LISP对AutoLISP扩展功能的部份,必须调用,vl-load-com 函 数 , 或 者 将 调 用 该 函 数 的 表 达 式 写 在,acad2006doc.lsp 文件内。,第2章,数据类型、表达式和函数,AutoLISP的数据类型丰富,除了一般程序设计语言具有的整,型、实型、字符串等类型之外,还有表、函数、文件描述符、,AutoCAD选择集、AutoCAD图元名、VLA对象、函数分页表和,外部函数等数据类型。,2.1 数据
7、类型,1. 整型(INT),整型即整数,整数由数字和正负号组成,正号可省略。整数为,32位带符号的数字,其范围从2147483648到2147483647。,2. 实型(REAL),实型数是带小数点的数。在-1和1之间的实数必须以0开头,例,如:.5是错误的,应该写成0.5。实数用双精度的浮点数表示,并,且至少有14位有效位数的精度。注意,Visual LISP不显示所有的,有效位。,3. 字符串(STR),字符串又被称为字符常数,它是由双引号括起来的字符序列,。字符串中字母的大、小写和空格符都是有意义的。,字符串中字符的个数(不包括双引号)称为字符串的长度。字,符串的最大长度为100,如果超
8、过100,第100个字符后面的字符,将是无效的。字符串可以是空的,即,称为空串,其长度为0,。,任何字符都可以用nnn的格式表示,其中反斜杠“”是ASCII码,的前导标识字符,nnn是该字符八进制的ASCII码。例如,字符,串ABCD也可表示为101102123104。一些常用的控制字符,象,反斜杠、双引号,除了可以用 nnn的格式表示之外,还可以用,“”转义字符的格式表示为“”、“”等特殊字符,见下表。,表2-1常用控制字符的表示方法,控制字符反斜杠“”双引号“”Esc键换行回车键Tab键,用“”为前导字符enrt,用“”为前导的ASCII码表示134042033012015011,注意,其
9、中的字符e,n,r,t必须小写。,4. 表,表以左圆括号开始,以配对的右圆括号结束,表可以是空的,,可以有若干个元素;元素可以是简单的,也可以是复杂的,,还可以是其它的表。例如:(+ 1 2 3)、(sin (* 0.5 pi),、(A B)、(A B) C (C D)、(0 LINE)、(101.5 2.6 0.0)、()都是合法的表。表中元素的个数称为表的长度。例如,表(+ 1 2 3)的长度为4、表(sin (* 0.5 pi)的长度为2、表(A B) C (C D)的长度为3、表()的长度为0。用表可以方便地构造出复杂的数据结构,例如,(1.5 3.6)可以表示为一个X等于1.5、Y等
10、于3.6的二维点,(2.5 2.0 1.0)可以表示为一个X等于2.5、Y等于2.0、Z等于1.0的三维点。,5. 函数(SUBS),函数相当于子程序或过程。函数分为内部函数和外部函数。AutoLISP提供的或用AutoLISP定义的函数为内部函数。用ADS、ADSRX或ARX定义的函数为外部函数。例如sin 、cos、sqrt为内部函数。,运算符在AutoLISP里属于函数,例如“”、“”、“*”、,“/”分别称之为加、减、乘、除函数,“”分别称之为小于、小于等于和大于函数。,其它计算机语言里的子程序、过程、程序流程控制的关字,在AutoLISP里也属于函数,例如“if”、 “while”分
11、别称之为条件和条件循环函数。,6. 文件描述符(FILE),文件描述符是AutoLISP赋于被打开文件的标识号,它类似于,文件指针。下面的例子是以“读”的方式打开文件myfile.dat,并,将该文件的描述符赋予符号f1。,(setq f1 (open myfile.dat r) 返回,7. 图元名(ENAME),图元名是AutoCAD为图形对象指定的16进制的数字标识。AutoLISP通过该标识,找到该图形对象在图形数据库中的位,置,以便对其进行访问或编辑。,8. 选择集(PICKSET),选择集是一个或多个图形对象命名的集合。可以通过,AutoLISP程序建立选择集、向指定的选择集添加或移
12、去图形对,象,通过选择集可以对其内指定的成员进行访问或编辑。,9. VLA 对象,VLA 对象是ActiveX 应用程序的主要组成部分。 不仅直线,、圆弧、多义线和圆等都被称为VLA对象,图层、组、块、视,图、视口、图形的模型空间、图纸空间、线型和尺寸标注样式,等也被称为VLA对象,甚至连AutoCAD本身也被认为是VLA对,象。,2.2 变量,2.2.1 符号,符号(SYMBOL)可以理解为标识,用来作为变量、函数的名字。它的命名规则是不能只含数字,可以由下列字符以外的任何可打印的字符所组成:,“(”、“)”、“”、“”、“”、“;”,例如,a1、b2 、c_3是合法的符号,(a、)b、.c
13、、4、5是非法的符号。注意:1、2、3、!4、/5、1a、3c、-d、+e、b-2、c*3也是合法的符号,这是与多数计算机语言的不同之处。,在AutoLISP中,符号的大小写等价,符号的长,度没有限制,且所有的字符都是有意义的。,如果一个符号的长度不超过6,就用节点本身来存,储;如果符号的长度超过6,这样的符号就不能用节,点来存储,而是在节点中存放一个指向实际存储符号,名的指针。这将会多占用存储空间,且减慢了执行速度。因此,符号的长度最好不要超过6。,2.2.2 变量的数据类型,AutoLISP变量属于符号,是指存储静态数据的符号。,数据类型是变量的重要的特征,因为它关系到存放变量值的,存储空
14、间的大小。多数的计算机语言都在为变量赋值前对变量做显式或隐式的说明。AutoLISP无须对变量做事先的类型说明,,变量被赋予值的类型即为变量的类型。,用setq函数对变量赋值,例如,(setq a 5),赋值后的结果是:变量a是整型的,因为5是整型的;同样的原因,(setq b 2.5)之后, b是实型的,(setq c ABC)之后,c就是字符串类型的。,在程序运行过程中,同一变量在不同的时刻可以被赋予不同类型的值,因此在程序运行过程中,变量的数据类型是可以被改变的。,例如,在一个程序里有以下两行:,(setq a 5)当前的变量a是整型的。,(setq a ABC) 当前的变量a是字符串类
15、型的。,用type函数了解变量的类型,例如,(type a ) 返回INT,显示变量a是整型的;(type b)返回REAL,显示变量b是实型的,(type c) 返回STR,显示变量c是字符串类型的。,2.2.3 为变量赋值,用setq函数为变量赋值,其格式如下:(setq 变量1 值1变量2 值2 . . .) 例如:Command:(setq x 1.5 y 20 p Center)返回“Center”除了为变量x、y、p赋值外,还返回最后一个表达式的结果,“Center”。如果上述表达式还有外层表达式,那么,将把该表,达式的返回值“Center”赋给外层表达式的变量。例如,,Comma
16、nd:(setq v(setq x 1.5 y 20 p Center) )返回“Center”该表达式同样为变量x、y、p赋值,并将内层表达式返回的结,果“Center”赋给变量v,最后返回外层表达式的结果“Center”。,没有被赋值的变量属于无定义的变量。,2.2.4 预定义的符号,AutoLISP对变量nil、T、Pause和Pi进行了预定义,用户可以,在编写AutoLISP程序时直接使用。,(1) nil,如果变量没有被赋值,它的值为nil。nil与空和0不同的是,它,既不是空串,也不是0值,它表示尚无定义。引用未被赋值的变,量是错误的。例如,变量x尚未被赋值,(+ 1 x)就会出现
17、error:,bad argument type(坏的参数类型错)。,将nil赋给某一有定义的变量,其结果是:取消该变量的定义,,并释放其所占存储空间。,nil作为逻辑变量的值,表示不成立,相当于其他程序设计语,言的false。,(2)T,T为常量,它作为逻辑变量的值,表示成立,相当于其他程,序设计语言的true。,(3)PAUSE,PAUSE与command函数配合使用,用于暂停,等候用户输,入。,(4)PI,PI被定义为常量 。每个变量都占用一些内存空间。为了节省存储空间,可重复,使用变量名,或者将不再使用的变量设为nil,以便释放该变量,所占的内存空间。,必须注意,一般的编程语言不允许将
18、内部函数名或,流程控制的关键字作为变量名,而AutoLISP没有这,样的限制。因此,程序中定义的符号名称不要与系统,定义的函数名和预定义的变量名相同,否则,后面的,定义将取代已有的定义,从而引起混乱。例如,sin是,正弦函数,可是在执行表达式(setq sin 1)之后,sin,不再是正弦函数,而是一个值为1的整型变量。,2.2.5 显示变量的值,要想在AutoCAD命令提示区显示变量的值,必须在变量名前添加惊叹号!。例如了解前面已赋值的变量x、v的值,操作如下:,Command:!x,1.5,Command:!v,Center,Command:!z,nil(假定变量z尚未被定义),R,2.2
19、.6 在交互方式下将变量的值传递给AutoCAD,在变量前加一个感叹号 “ ! ” ,即可将表达式的值传递给,AutoCAD。例如:,Command:(setq p (20 10) r 5)Command:circleSpecify center point for circle or 3P/2P/Ttr(tan tan radius):,!p,Specify radius of circle or Diameter::!r即可画出圆心的坐标为(20,10)半径为5的圆。,2.3 表达式,AutoLISP处理的对象是符号表达式(简称表达式)。表达式相,当于其他编程语言中程序的语句。,2.3.1
20、 表达式的构成,表达式是由原子或表构成的。原子可细分为数原子、串原子和,符号原子。数或串原子的值是数或串本身,符号原子的值是赋给,该符号的值。例如:5、12.5、“ABC是单个原子构成的表达式。,多数情况下,表达式以表的形式存在,其格式如下:(函数名 变元)变元的数量可能为0,也可能任意多个,这取决于具体函数。每,个参数还可以是一个表达式。,表达式形式的表,左圆括号之后的第一个元素必须是函数名。,2.3.2 表达式的前缀表示法,大多数计算机语言的表达式采用中缀表示法,即运算符在操,作数中间。如,x=2.5,y=1+2。,AutoLISP采用前缀表示法,将函数名放在所有操作数之前。,如,(set
21、q x 2.5),(setq y(+ 1 2)。,若将x=(a+b)*c 改写为AutoLISP表达式,则为:(setq x(* (+ a b) c)前缀表示法使得运算和函数调用的形式得到了统一。前面提,到,在AutoLISP语言里,运算符也属于函数。,2.3.3 表达式的求值过程,在LISP语言中,函数之间不存在是否优先的关系,运算的先后顺序只能通过表的层次来实现,最里层的表最先被求值,把求值的结果返回给外层表,直至求值完毕。,例如,表达式(setq x(* (+ a b) c),先求出最内层a与b之和,然后求出a、b之和与 c的积,将求得的积赋给x,最后返回x的值。,可以在Command:
22、提示下,输入一个表达式,AutoCAD将计算该表达式并返回计算结果。AutoCAD至多显示6位小数。,例如,在Command:提示下,键入(sin 0.5)之后回车,将,返回0.479426。,如果输入的或者从文件中读入的表达式不正确,将显示出错信,息,最常见的出错信息是:,(_,表示缺少与左圆括号匹配的右圆括号,“(”的个数即为缺少右,圆括号的数量。如果出现该信息,输入与所提示的左圆括号相等,的右圆括号既可。由于所缺的右圆括号不一定都是最后的,所以,可能产生错误的结果。,如果遗漏了与左端双引号匹配的右端的双引号,显示的出错信,息为:,(_在这种情况下输入匹配的双引号也不一定使表达式能正常求值
23、,。此时只能按Esc键终止当前的输入,重新输入表达式。,2.3.4 表达式的求值规则,(1)整型数、实型数、字符串,以它们本身的值作为求值结,果。,(2)符号以它们当前的约束值作为求值结果。(3)表是根据其第一个元素来进行求值的。 如果第一个元素或第一个元素的计算结果是一个函数名,,那么以表中剩余的元素作为该函数的变元,计算出该函数的值,。例如,表达式:,(+ (* 2 3) (/ 50 3)先计算最内层的表达式(* 2 3)和 (/ 50 3),将结果6和16,返回给其外层表达式,原表达式变为:(+ 6 16),继续计算表达式(+ 6 16),返回22。, 如果第一个元素是一个表,该表不是调
24、用而是定义函数,,若语法正确,首先定义这个函数,然后继续表达式求值。, 如果第一个元素既不是函数名,也不是定义函数,将停,止求值并显示出错信息。,例如(25 a b c),将停止求值并显示“error: bad function:,25”,因为25是非法的函数名,所以显示25是坏函数的出错信,息。,又比如(fx a b c) ,将停止求值并显示“error: no function,definition: FX” ,指出没有定义fx这个函数。, 用quote函数可以禁止对表求值。对于不需求值而直接整体引用的表,例如将一个表示三维点,的表(3 2 1)赋给变量p,如果表达式为(setq p(3
25、2 1),将中断求值过程并显示“error: bad function: 3”出错信息。因,为首先对内层表求值,而内层表的第1个元素“3”不是函数名,,所以停止求值。此时应该用quote函数,将表达式改为(setq p,(quote(3 2 1),这样就不再对表(3 2 1)求值,而是,将将表(3 2 1)整体赋给变量p,使之表示一个3维的点。,quote是AutoLISP程序中使用最多的函数,因此该函数可用,一个单引号“”表示。例如(quote (10 20)可以表示为(10 20),。如果将(10 20)这个2维点赋给变量p2,可写成:,(setq p2 (10 20),2.4 数据的存储
26、结构,计算机的内存是由许多编了码的内存单元组成。一个特定内,存单元的编号称为内存地址。内存单元的内容是数字,也可以是,内存单元的编号,即另一内存单元的地址。如果一个内存单元的,内容是另一个内存单元的地址,那么,这个内存单元被称为是指,向另一个内存单元的指针。如果一个内存单元分为左、右两部分,,分别存放两个内存单元的地址,那么,这个内存单元就具有左,、右两个指针。,这种具有左、右两个指针的内存单元被称为节点。每个节点,的长度是12个字节,等分为左、右两部分,作为左、右指针,每,个节点都有它的地址。AutoLISP通过这样的一些节点构成链表,,以链式方式存储各种数据。,1. 符号,创建一个符号,例
27、如(setq radius 10),至少需要3个节点。,一个节点链接到符号原子表的链尾并指向存放符号名的节点,,一个节点存放符号名和存放符号值的指针,最后一个节点存放,符号的值,见图(a)。,如果符号的长度超过6字符,例如(setq fillet_radius 5),需,要申请存放符号名的存储空间,用原来存放符号名的半个节点,作为指向存放符号名的指针,见图(b)。显然要多占用一些存储,空间。,2. 字符串,字符串在内存中是以连续的空间存储的。,3. 表,表通过一组节点来存储。这些节点用右指针指向各自下一个元,素的地址,最后一个节点的右指针为空,用左指针指向各自的元,素。,下图依次是表(A B
28、C D)、(A (B C)(D E) 和(setq x(,+ (* a)( d)的存储结构。,4. 点对(dotted pair),点对是一种特殊的表。若表只有两个元素,且每个元素都是原子,这样的表可以用点对表示。点对的形式为(原子 . 原子),如(0 . LINE)、(8 . A1)、(40 . 15.0)。用一个结点存放点对,结点的左指针指向第一个元素,右指针指向第二个元素。图是只有两个元素(元素为原子)的表的存储结构,图(b)是点对的存储结构。从图中不难得出结论,两个元素都是原子的表,用点对会节省存储空间。点对的另一个特点是简化了某些函数对表的运算。由于点对具有这些优点,因此是,Auto
29、LISP常用的数据结构。,2.5 函数,前面提到,一般计算机语言所说的函数,AutoLISP照常称之为,函数;一般计算机语言里的子程序、过程、运算符、程序流程控,制的关键字,在AutoLISP里都被称之为函数。,AutoLISP将函数分为内部和外部函数。AutoLISP提供的或用,AutoLISP定义的函数为内部函数。用ADS、ADSRX或ARX定义,的函数为外部函数。,2.5.1 定义AutoLISP函数,定义函数用defun函数。格式如下:,(defun 函数名 (变元. / 局部变量. ) 表达式 .),函数名:函数名和变量名的命名规则相同,是代表一个函数的,符号,不应与现有的AutoL
30、ISP函数同名,否则现有函数的功能被,新定义函数的功能所取代。,变元:变元即该函数的参数,变元的数量根据实际需要而定,,可以没有变元,但不能省略一对括号“()”。,局部变量:在定义函数时,除了用到函数的参数之外,还可能,用到其他一些变量。在该域列举这些变量的名字,这些变量就,成为局部变量。局部变量是指局限于该函数内部所用的变量,,它只在该函数调用期间得到定义,函数调用结束,局部变量的,值均为nil,同时释放其所占存储空间。如果不作声明,它们将,成为全程变量,即使函数调用结束,仍然保留各自的值,可被,其他函数所存取,直至退出当前的图形文件。进行局部变量声,明不仅可以节省存储空间,而且可以避免函数
31、之间相互干扰。,局部变量与变元之间用除号隔开,即使没有变元,也可以有局,部变量说明,但前面要有除号。,表达式:表达式的数量不限,用于描述该函数的运算。函数的返回值:最后一个表达式的返回值即为该函数的返回,值。,【例2-1】定义一个加10函数,源代码如下:,(defun add10(x),(setq x(+ 10 x),),或者:,(defun add10(x),(+ 10 x),),该函数的函数名是add10,只有一个变元x,没有局部变量说,明,它返回表达式(setq x(+ 10 x)或(+ 10 x)的值(二者是相等,的)。,v(*,【例2-2】定义一个已知圆柱体的半径和高度,计算其,体积
32、的函数。源代码如下:,(defun volume(r h / v),(setq v(* r r pi h),),该函数的函数名是volume ,有两个变元r(半径)和h(高度),有一个局部变量v,它返回表达式(setq,r r pi h)的值。,2.5.2 调用AutoLISP函数,AutoLISP以表的形式调用函数,其格式如下:(函数名 变元)表的第一个元素是函数名,其余是该函数所要求的变元,变元的,数量可能为0,也可能任意多个,这取决于具体函数。例如,,(sqrt 4),调用的函数名为sqrt(求平方根),函数的变元是4,、只有一个变元;,(+ 1 2 3),调用的函数名为“+”(加),函
33、数的变元是1、2、,3,共有3个变元。,每个变元还可以是一个表达式。例如:(sqrt (+ 1 2 3)。,每调用一个函数,都会得到函数的返回值。例如,,(sqrt 4) , 返回2(+ 1 2 3) , 返回6,有些函数返回逻辑常数T或nil,例如,,( 1 2), 返回T;,(not 1),返回nil。,调用自定义的函数与调用系统提供的函数的格式相同。例如,(add10 3),(setq y(add10 (+ 2 3),(volume 12.5 20)1),2.5.3 递归调用AutoLISP函数,AutoLISP可以在一个函数的内部定义另一个函数,可以递归,定义和调用函数。,【例2-3】
34、定义阶乘函数,阶乘的定义是,0的阶乘等于1,n的阶乘等于n(n-1)的阶,乘。程序的源代码如下:,(defun factorial(x)(if (= x 0)1(* x (factorial (- x 1),2.5.4 调用AutoCAD命令,AutoLISP用command函数调用AutoCAD命令,其格式如下:,(command “AutoCAD命令” 命令所需的数据 ),【例2-4】绘制以(2,4)为圆心,5为半径的圆。,(command circle 2,4 5)或者,(command circle (2 4) 5),【例2-5】已知p1、p2、p3为不在同一直线上的三个点,利用这,三
35、个点画圆。,(command circle 3p p1 p2 p3),【例2-6】画从点p1(0,0)到点p2(10,0)的一段直线。,(command line“ 0,0“ 10,0“ )或者,(command line (0 0) (10 0) )或者,(command line (list 0 0) (list 10 0) )或者,(command line“ p1 p2 );假定p1、p2是两个点,注意:因为绘制直线时用空回车或空格响应 “Specify next point,or Undo:”提示才能结束line命令,所以在最后一个点的后面要,增加两个双引号,代表一个回车或空格。,2
36、.5.5 定义AutoCAD命令,定义AutoCAD命令用defun函数。格式如下:(defun C:AutoCAD命令名 (/ 局部变量. )表达式 .)定义AutoCAD命令与定义函数基本相同,不同之处是,:, 命令名前加“C:”。 没有变元,但可以有局部变量说明。此外要注意所定义的AutoCAD命令不应与现有的,AutoCAD命令同名。,【例2-7】定义输入矩形的对角点绘制矩形的命令。,源代码如下:,(defun c:rect1( / p1 p2 p3 p4) ;p1、p2、p3、p4是局部变元,(setq p1 (getpoint n输入矩形的一个角点 )(setq p3 (getco
37、rner p1 n输入矩形的另一个角点: ),(setq p2(list (car p3) (cadr p1)(setq p4(list (car p1)(cadr p3)(command pline p1 p2 p3 p4 c),p4,p3,),p1,p2,加载含有以上代码的程序后,在AutoCAD的Command:命令,提示下,象调用普通命令一样键入rect1,即可调用自定义的绘,制矩形的命令(见下图)。,第3章,程序的流程控制和,AutoLISP文件,3.1 程序的流程控制,AutoLISP程序的流程通过流程控制函数控制。,3.1.1 分支结构,1. (cond (测试表达式1 结果表达
38、式1) (测试表达式2 结果表达式2) ),该函数从第一个子表起,计算每一个子表的测试表达式,直至有一个子表的测试表达式成立为止,然后计算该子表的结果表达式,并返回这个结果表达式的值。,例如,当 i 小于等于1时,n=1;小于等于2时,n=4;小于等于3时,n=10;其它情况下n=100。用cond函数实现变量n和i 之间以上关系的源代码如下:,成立,测试表达式1不成立成立测试表达式2不成立成立测试表达式n不成立,结果表达式1结果表达式2结果表达式n,成立,测试表达式1不成立成立测试表达式2不成立成立测试表达式n不成立T(其余),结果表达式1结果表达式2结果表达式n结果表达式,(setq n(
39、cond (= i 1) 1),(= i 2) 4)(= i 3) 10),(t 100),),说明:该函数类似于C语言的switch语句, 最后一个测试表达式“t(或T)”相当于C语言的“default”,指其余的情况,例如i 等于5时,n等于100,也可以缺少这个测试表达式。与C语言switch语句不同的是,若某一测试表达式成立,即返回相应结果表达式的值,不再向下测试。例如,上述表达式中,若i 等于0,已满足第一个测试表达式( a 1) (setq b 2) ),该表达式的含义是;如果a 大于 1,则b等于2,否则不作任何,计算,求值结束。,(if ( a 1) (setq b 2) (s
40、etq b 3) ),该表达式的含义是;如果a 大于 1,则b等于2,否则b等于3,,求值结束。,注意:该函数最多只有3个变元,即测试条件表达式、表达式1和,表达式2。先分析下列程序代码:,测试条件表达式,测试条件表达式,成立表达式1,成立表达式1,不成立表达式2,(if ( a 1),(setq b 2)(print ( b a)(setq b 4)(print b),),该程序段的本意是,若条件成立,b等于2,然后打印a与b之,和,否则b等于4,然后打印b。但是在执行该程序段时,首先检,查if函数变元的数量,第1个变元是测试条件表达式,它对应表达,式( a 1),第2个变元是条件成立时执行
41、的表达式,它对应表达,式(setq b 2),第3个变元是条件不成立时执行的表达式,它对应,表达式(print ( b a) ,那么后面的两个表达式就是多余的变元,,因此显示“too many arguments(变元太多)”的出错信息。,若将该程序段改写为以下代码:,(if ( a 1),(setq b 2)(print ( b a)(setq b 4)(print b),),用括号将条件成立或不成立的多个表达式括起,虽然变元的,数量改为3个,但会出现“error: bad function: 2”的出错信息。原,因是在计算条件成立的表达式时,首先计算内层表(setq b 2),,将其返回值
42、“2”作为外层表的函数名,显然这是一个坏的函数名,。利用progn函数可以很好地解决了本程序段存在的问题。,3. (prong 表达式.),该函数将n个表达式组合起来,作为if函数的一个表达式。在执行时,按顺序计算n个表达式,返回最后一个表达式的计算结果,。例如:,(if ( a 1),(progn (setq b 2)(print ( b a)(progn (setq b 4)(print b),),该程序段的执行过程是,若条件成立,b等于2,然后打印a与,b之和,返回a与b之和;若条件不成立,b等于4,然后打印4,返回4。,3.1.2 循环结构,1. (repeat 整数n 表达式 ),重
43、复执行n次,对所有的表达式求值,返回最后一个表达式的,计算结果。例如:,(setq a 1 b 100),(repeat 10,(setq a(1+ a)其它表达式 (setq b(+ 10 b),),执行结果:a等于11,b等于200,返回值为200。,2. (while 测试式 表达式 ),若测试结果不为nil,执行各表达式, 直至测试结果为nil。例如:,(setq i 1 a 10),(while (= i 10),(setq a(+ a 10)其它表达式 (setq i (1+ i ),),执行结果:i等于11,a等于110,返回值为11。,例如,定义求解百钱买百鸡的函数。题目是若母
44、鸡每只3个钱,,公鸡每只2个钱,小鸡每只0.5个钱。用100个钱买100只鸡,有,几个答案,每个答案各有几只母鸡、公鸡和小鸡(不包括0只),,打印所求的结果。,该例没有合适的计算公式,只能利用枚举,试出合适的结果,。首先分析母鸡数量的范围,如果母鸡等于20,剩余40个钱。用,剩下的钱至少买1只公鸡之后,可以买76只小鸡,但鸡的总数为,97(小于100),所以母鸡的数量应小于20。同样,买了至少1只,母鸡和32只公鸡之后,剩余的33个钱最多买66小鸡,鸡的总数为,99(小于100),所以公鸡的数量应小于32。小鸡的数量只能是,100减去母鸡与公鸡之和。当公鸡的数量和钱数都等于100时,打,印这组
45、解。,【例3-1】解百钱买百鸡程序,(defun chicken( / hen cock chick cost),(setq hen 1)(while ( hen 20);母鸡的数量不超过20(setq cock 1)(while ( cock 32);公鸡的可能数量不超过32(setq chick (- 100 hen cock);小鸡的数量(setq cost (+ (* 3 hen)(* 2 cock)(* 0.5 chick);3种鸡的钱数(if (= cost 100)(print (list 母鸡= hen 公鸡= cock 小鸡= chick)(setq cock (1+ coc
46、k);公鸡的数量加1)(setq hen (1+ hen) ;母鸡的数量加1),(princ);静默退出,),说明:, 程序的第3行(setq hen 1)不可缺少,否则第4行(while ( hen 20),中的hen将是无定义的。, 第5行(setq cock 1)若改写在第3行(setq hen 1 cock 1),虽然没,有语法错误,但内层循环cock的值第一次从1增加到32后,就固,定为32,出现了算法错误。, 程序的第12行(setq cock (1+ cock)不能只写成(1+ cock),表达,式(1+ cock)返回cock+1的值,但cock的值不变,这是1+ 函数与C,语言 + 运算的不同之处。, 第10行(print (list “母鸡=” hen “ 公鸡=” cock “ 小鸡=” chick)若,写成(print “母鸡=” hen “ 公鸡=” cock “ 小鸡=” chick)是错误的,,因为print函数只需一个变元。,