Emacs折腾日记(四)——elisp控制结构
目前我们接着学习elisp相关语法,这里我是按照 elisp 简明教程 来进行学习。与其说这是我自己写得教程到不如说是在这个上面做得注释。目前我不知道这样是否侵犯相关的知识产权。目前就先这样继续学习,继续写记录吧。
闲话少说,进入本篇的正题,关于elisp的控制结构。一般编程语言都有三种控制结构:顺序结构、条件结构、循环结构。elisp同样有这三种控制结构。
顺序结构和复合语句
一般默认elisp的语句是顺序执行的,例如下面的代码
(setq name "Emacs")
(message "hello, %s" name)
它先执行前面的 setq
语句,先给变量name
定义并赋值为 Emacs
。后面接着执行第二行代码,调用message
函数来输出一段文字。
在其他语言一般都有一个复合语句。它是有多个语句共同组成的,例如 C/C++中使用{}
来将多个语句整合成一条复合语句。针对C/C++ 我们在很多地方会用到复合语句。例如如果 if
, while
等语句后只需要一条语句,那么可以直接使用一条语句,例如下面的代码
// 这么写代码不太正规但是符合语法规范,也能编译过
int main()
{
int i = 0;
while(i++ < 10)
printf("%d\n", i); //打印1到10,这么10个数字
return(0);
}
但是如果在循环或者if条件成立后,执行多条语句,就需要使用复合语句,也就是用大括号括起来。
那么在elisp中也有这样的操作,在条件和循环语句中需要执行不止一条语句,也需要使用复合语句。
elisp 中符合语句使用 progn
来包含一组语句组成复合语句,它的语法规则是
(progn
statement1
statement2
...
statement3)
例如我们将上面的代码用 progn
包装一下
(progn
(setq name "Emacs")
(message "hello, %s" name)) ;; => "hello, Emacs"
使用 progn
包装的复核语句可以使用 C-x C-e
也就是 eval-last-sexp
来同时执行里面的两个子语句。如果我们将它们分开写,则使用 eval-last-sexp
做不到这点,它只能一条条的执行
条件语句
我们使用 if
和 cond
来表示条件分支,if的语法如下
(if condition
then
else)
需要注意的是 这里的 then
和 else
并不是关键字,而是对应的语句,也就说紧跟着if条件的语句表示条件成立时执行的代码,下一条则是条件不成立时执行的代码。例如我们使用下面的代码来获取两个数的最大值
(defun get-max(a b)
(if (> a b)
a
b))
(get-max 3 4) ; => 4
与 C/C++ 的函数不同,elisp 函数的返回值不需要使用 return
或者其他的关键字特意指出,它是将函数最后执行的语句的返回值作为函数的返回值,这里当 a > b
时条件成立,执行 a
然后结束函数,也就是这个时候函数的最后一个语句是 a
,函数返回 a
的值。否则执行 b
,此时函数的最后执行的语句就是 b
,这个时候函数就返回 b
的值
而 cond
有点像 C/C++
中的 switch
,它的语法如下
(cond (case1 do-when-case1)
(case2 do-when-case2)
...
(t do-when-none-meet))
它的语法特点是,它与 switch
类似,由一堆 case
和 default
组成。每个case 都使用一对 ()
来区分,最后可以使用 t
来表示未匹配到前面的 case
时执行的语句,类似于default
语句。这里我们使用当初学习C/C++
switch
语法时的经典代码来作为示例
(defun score-report (score)
(cond ((>= score 90) "优秀")
((>= score 80) "良好")
((>= score 60) "及格")
(t "不及格")))
(score-report 75); => 及格
我们可以看到,cond
语句的使用比 switch
更为的灵活,switch case
只能进行整型变量的相等比较,而 cond
可以进行其他变量类型的不同形式的条件判断,它只是在形式上更像 switch
,但是在使用的范围上更像 if-else if-else
。另外 elisp 简明教程中 提供了一个使用 cond
计算 斐波那契数列的例子
(defun fib(n)
(cond ((= n 0) 0)
((= n 1) 1)
(t (+ (fib (- n 1)) (fib (- n 2))))))
(fib 10) ; => 55
因为 elisp 中使用
setq
来进行赋值操作,所以它里面的=
就是数学意义上比较相等的操作符,而 其他语言中的==
在lisp中无效。这里如果写成==
将会报错。
上面的例子也很好理解 当 n
等于 0时返回0,等于 1 时返回1,否则返回 fib(n - 1) + fib(n - 2)
使用 C/C++
的话可能更容易理解
int fib(int n)
{
if(i == 0)
return 0;
else if (i == 1)
return 1;
else
return fib(n - 1) + fib (n - 2)
}
循环结构
循环使用 while
关键字,它的语法结构如下
(while condition
body)
我们可以将上述循环打印的C代码使用 elisp
实现
(setq i 0)
(while (< i 10)
(progn
(message "%d" i)
(setq i (+ i 1))))
我们执行完代码之后使用 switch-buffer
,切换到 *message*
,可以看到它打印了从0到9的数据。上面的斐波那契数列的例子我们可以使用 while
来实现
(defun fib (n)
(cond ((= n 0) 0)
((= n 1) 1)
(t (let ((first 1)
(second 1)
(third 1))
(setq n (- n 2))
(while (> n 0)
(progn
(setq third (+ first second))
(setq first second)
(setq second third)
(setq n (- n 1))))
third))))
(fib 10) ; => 55
因为 elisp 中没有提供
+=
++
这样算术运算符,所以我们需要使用setq
来赋值。
下面还有一个计算阶乘的例子
(defun factorial (n)
(let ((res 1))
(while (> n 1)
(setq res (* res n))
(setq n (- n 1)))
res))
(factorial 10) ; => 3628800
我们也可以提供一个递归的版本
(defun factorial (n)
(if (= n 1)
1
(* (factorial (- n 1)) n)))
(factorial 10) ; => 3628800
到此为止,本篇就结束了。本篇涉及到的elisp 代码其实也不算复杂,如果能熟练掌握一门编程语言的话,到此为止的代码应该不算太难理解。在编写这些示例代码的时候我觉得还好,主要注意括号的匹配,算法什么的就是照搬C/C++中一些经典写法就差不多了。但是即使上面的代码并不多,代码量并不大,我也能明显感觉到上述代码在阅读上不那么直观。