Lisp编程中值得记录的地方
本帖最后由 Zrrrrr 于 2020-10-5 17:43 编辑此贴用于总结下自己在编程过程中记得记录的点,主要是一些自己学习时觉得不好懂的地方,或是觉得很厉害的写法,自己很难想到之类的。
目录:
二维表的操作(两级mapcar的使用)
mapcar+apply的使用
如何在利用函数的输入参数符号来返回值?
在函数中自定义自身的技巧
用and来代替if的方法
后面想到了再更新
二维表的操作(两级mapcar的使用)
对于一个形如((1 2 3) (4 5 6) (7 8 9))的表,可以认为是一个矩阵,或者说二维表,即表中每一个元素都是一个表。对于这种表,可以使用两级循环来操作,也可使用两级mapcar来操作。
比如,要对上述的表,实现每个元素+1的操作,可以使用两级mapcar这样:
(setq ll '((1 2 3) (4 5 6) (7 8 9)))
(mapcar
'(lambda (row)
(mapcar
'1+
row
)
)
ll
)
刚开始编程时,可能会对上述写法存在理解上的困难。从我自己的学习经验来看,理解的关键就是:1. mapcar函数有两个参数(不准确,可以是任意可,但此处为理解方便,做简化处理):第一个参数是一个函数(常为lambda函数),第二个参数是一个表。2. 外面一级mapcar中的第二个参数,即一个表,表中每个单独的元素,作为进入这一级mapcar函数的第一个参数即lambda函数的输入参数row,这个参数row又是作为里面一级mapcar函数的list参数使用的,这样row里面的每一个参数又进入第二级mapcar的lambda函数(在本例中为1+这个函数)作为函数的输入参数使用。
上面写了不少可能也还是不好理解的话,只有自己动手试一试,练习一下,慢慢体会。
mapcar+apply的使用
大家考虑这样一个例子,如果要将一个二维表转置,该如何操作呢?对于((1 2 3) (4 5 6) (7 8 9)),转置后,应该是((1 4 7) (2 5 8) (3 6 9)),该如何实现呢?经过思考可以得到,可以这样写:(mapcar 'list '(1 2 3) '(4 5 6) '(7 8 9))。但问题在于,我们拥有的数据是一个二维表,但刚才得到的函数,其输入参数却是把这个二维表拆分之后的,需要输入三个参数,怎么才能实现呢?难道需要(mapcar 'list (nth 0 aa) (nth 1 aa) (nth 2 aa))?这样写难免也太笨拙了。其实,对于这种情况,使用mapcar+apply即可解决:
(apply 'mapcar (cons 'list m))
这样一句话,即可实现类似把输入参数二维表拆分的效果。
如何在利用函数的输入参数符号来返回值?
不知大家有没有觉得vla-GetBoundingBox函数很特别,居然没有返回值,可以设定变量的值?这个是如何实现的?说实话,我也只是朦胧知道个大概,具体为什么也说不明白,但如果想要使用这种方法,可参考如下代码:
(defun tt (*a / ) (set *a 1))
(tt 'bb) ;;特别注意:调用时,需要在参数前加'
运行(tt 'bb)会发现b已经被赋值为1了
但这样就形成了一个全局变量,可采用以下写法避免:
(defun tt1 ( / bb)
(tt2 'bb) ;;特别注意:调用时,需要在参数前加'
(princ bb)
(princ)
)
(defun tt2 (*a / ) (set *a 1))
运行(tt1),可以输出1,但运行后检查bb的值还是nil,避免了产生全局变量。
注意:这个写法还有个要注意之处,就是调用时的参数,和函数里定义的参数,不能是同一个变量名。有如下示例代码:
(defun tt1 ( / bb)
(tt2 'bb)
(princ bb)
(princ)
)
(defun tt2 (bb / ) (set bb 1))
在这个写法下,运行(tt1),输出为nil。
总结一下,就是如果需要在函数调用时把参数作为返回值,就需要:1. 调用时加';2.在函数定义里赋值的时候用set而不是setq;3. 调用时采用的变量名不能是函数定义里的变量名。
采用这种写法的好处在于,当返回值时两个及以上的数时(比如输入一个直线对象,返回其两个端点),这样写更简洁。
在函数中自定义自身的技巧?
LeeMac的LM:acdoc函数如下:
(defun LM:acdoc nil
(eval (list 'defun 'LM:acdoc 'nil (vla-get-activedocument (vlax-get-acad-object))))
(LM:acdoc)
)
可以看到,函数定义里居然重定义了自身,为什么要这样呢?可以这样思考,要实现同样的功能,常规做法是:
(defun Bad:acdoc nil
(vla-get-activedocument (vlax-get-acad-object))
)
但这样写,每次都需要进行相应的求值,先获取acad对象,再获取activedocument对象。
但采用LeeMac的写法,仅在函数运行的第一次,进行了求值,第二次及以后调用时,直接返回那个值就可以了,提高了效率。
不过这种写法,在其他什么地方能用上,个人暂时还没想到,反正感觉就是一个函数第一次运行和后续运行需要有区别时,可以这样考虑。
用and来代替if的方法
试想这样一段程序:
(if (and (条件1) (条件2))
(progn
(执行1)
(执行2)
)
)
在一些人的程序里,往往可以看到这样的等价写法:
(and
(条件1)
(条件2)
(执行1)
(执行2)
)
置于为什么这样写,我个人并不清楚,认为是不是这样看起来更简单?或者说,省了一个progn?而且这样写,这个判断+执行的程序片段,似乎是不方便返回值的,因为and只能返回t或nil。但总的来说,也算是一种方法。如果有高手来解释一下更好。
本帖最后由 菜卷鱼 于 2020-10-7 17:51 编辑
以下是我个人关于and的想法:
最近,我写代码的时候,有时候会用and代替if progn
因为(and 表达式1 表达式2 表达式3 ....) 只要有任何一个 表达式返回nil ,后面的表达式都会停止求值,
但是(progn 表达式1 表达式2 表达式3 ....) ,不管表达式结果是什么,会把所有表达式跑一遍,
比如:程序先对表达式1求值,然后表达式2要用要表达式1的返回值,但是表达式2要用到的参数不能是nil,刚好表达式1返回的是nil ,这个时候程序就会报错,停止运行
(and
(setq ss (ssget "x" '((0 . "insert"))))
(sslength ss)
)
(progn
(setq ss (ssget "x" '((0 . "insert"))))
(sslength ss)
)
假如ss 是nil ,progn 执行结果会报错,会导致整个程序停止运行,而 and 执行只会返回一个nil 。
程序的可读性 可维护性 远比花里胡销的难以理解的写法要紧 本帖最后由 urings 于 2020-10-8 00:39 编辑
个人理解的AND替换IF
http://bbs.mjtd.com/thread-182417-1-1.html and函数这样使用
主要是为了减少程序中使用if进行判断的次数 本帖最后由 wzg356 于 2020-10-6 12:44 编辑
(if (and (条件1) (条件2))
(progn
(执行1)
(执行2)
)
)
----------
(and
(条件1)
(条件2)
(执行1)
(执行2)
)
二者并不一定等价
前者顺序执行(执行1)(执行2),后者当(执行1)==》nil时不执行2,需要写成
(and
(条件1)
(条件2)
(or (执行1) t)
(执行2)
)
有时程序中有需要临时画参考图元时,之后需要执行(entdel(entlast)),为确保不误删,需要这样写才保险
(and
(条件1)
(条件2)
(画参考图元)
(or (执行其他) t)
(entdel(entlast))
) 址传递这个倒是个之前没注意到的部分,谢谢大佬. 深入研究,收获不少。 vectra 发表于 2020-10-8 15:18
程序的可读性 可维护性 远比花里胡销的难以理解的写法要紧
非常赞同大神的观点
页:
[1]