首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
302 阅读
2
从零开始配置 vim(15)——状态栏配置
176 阅读
3
nvim番外之将配置的插件管理器更新为lazy
165 阅读
4
2018总结与2019规划
154 阅读
5
wsl 启动报错:Wsl/0x80080005
132 阅读
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
登录
Search
标签搜索
c++
c
emacs
elisp
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
读书笔记
VimScript
linux
文本编辑器
投资理财
Java
反汇编
OLEDB
数据库编程
Masimaro
累计撰写
471
篇文章
累计收到
33
条评论
首页
栏目
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
页面
归档
友情链接
关于
搜索到
102
篇与
的结果
2024-12-17
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++中一些经典写法就差不多了。但是即使上面的代码并不多,代码量并不大,我也能明显感觉到上述代码在阅读上不那么直观。
2024年12月17日
36 阅读
0 评论
0 点赞
2024-12-11
Emacs折腾日记(三)——简单的elisp 入门
Emacs本身的使用并不复杂,利用帮助文档,差不多半小时左右就能把一些常见的操作方式和快捷键过一遍,剩下的就是慢慢使用并且熟悉了。Emacs真正有价值的是它高度的客制化。任何人都可以利用elisp代码将Emacs改造成只属于自己的编辑器。会elisp 的不一定是高手,但是高手没有一个是不会elisp的。学习Emacs也绕不开elisp。下面我们就来简单的学点elisp一个简单的 Hello Word(message "hello world")这是一个简单的elisp版本的hello world程序。麻雀虽小但是五脏俱全。从这个简单的程序来说我们可以看出lisp的最大特点,就是以括号作为作为一个完整的表达式。曾今有一个段子说苏联的特工冒死偷到了美国阿波罗计划代码的最后一页,结果回来一看全是括号。这也鲜明的表达了一个lisp的特点,那就是大量的括号我们简单的解析一下上面的代码,上面的代码调用了一个函数,函数的参数就是一个字符串的 hello world。我们可以打开Emacs,进入 scratch buffer,输入上述代码。之后可以将光标移动到代码尾部,按下快捷键 C-x C-e 来执行代码,或者使用 M-x 输入命令 eval-buffer 来看效果。我们可以看到,mini-buffer 位置出现了 "hello world" 的字符串变量我们可以使用 setq 来定义变量,它类似于C/C++ 中的= ,用来给变量赋值或者定义并初始化一个变量。例如我们可以改一下上述的代码(setq name "Emacs") (message "hello, %s" name)将上述代码输入到scratch buffer之后就需要依次在每行的最后执行 C-x C-e 或者直接执行eval-buffer 命令,这样就可以看到效果了上面我们说,elisp的表达式是使用括号来表示的,但是如果在上面代码的基础上加上一句(name)此时就会报错,显示的错误为 void-function name 。我们的本意是想让解释器返回name的值,但是解释器将它作为了一个函数。通过这个错误我们能了解到,elisp基本的表达式中函数需要用括号括起来,但是变量自己本身被作为一个完整的表达式。除了使用setq我们还可以使用 defvar 来定义变量,defvar 的使用如下(defvar variable-name value "variable document")例如(defvar name "Emacs" "a defvar demo name") name ;; ==> "Emacs"我们将光标放到name上,按下 C-h v 可以看到关于name的说明文档。需要注意的是,defvar与setq 除了defvar可以指定变量的说明文档外,还有一个区别就是defvar在定义变量前,这个变量已经有值的话,defvar不会改变变量的值例如下面的例子(setq foo "foo") (defvar foo "this is foo" "document for variable foo") (defvar bar "this is bar" "document for variable bar") foo ;; =>"foo" bar ;; =>"this is bar"C-x C-e(eval-last-sexp) treatsdefvarexpressions specially. Normally, evaluating adefvarexpression does nothing if the variable it defines already has a value. But this command unconditionally resets the variable to the initial value specified by thedefvar; this is convenient for debugging Emacs Lisp programs.defcustomanddeffaceexpressions are treated similarly. Note the other commands documented in this section, excepteval-defun, do not have this special feature.上述英文翻译过来就是 eval-last-sexp 对 defvar 做了特殊处理,默认情况下 defvar 在变量有值的情况下不做任何操作,但是在这个命令中,defvar 会无条件的将变量值重置为它指定的值。主要是为了方便调试代码。同时 defcustom 和 deface 做了同样的操作。请注意在本节中记录的其他命令(eval-defun 除外)没有做这样的处理这就解释了为什么我们使用 C-x C-e 执行的时候 foo 的值发生了改变函数函数的定义与使用作为一门函数式编程语言,函数是elisp的一等公民。如何使用一个函数我们已经在前面的hello world程序中见识过了,那么如何定义一个函数呢?定义一个函数使用 defun 关键字。它的语法如下(defun func-name(args) "document string" body)第一行代表一个函数名和函数的参数列表,第二行表示可以使用一个双引号包含函数的说明文档,Emacs是一个自文档的系统,后续我们可以查看这里写的文档。最后一行是函数的主体内容,例如下面的例子(defun say-hello(name) "say hello to define user" (message "hello, %s" name)) (say-hello "emacs") ;; => "hello, emacs"执行将会输出对应的信息。我们将光标移动到say-hello这个函数上,执行 C-h f 默认回车将会得到我们针对函数写的文档lambda 表达式其实像C++、Java、Python 之类的语言也有lambda表达式,它就是一个匿名函数。一般我们使用函数都是先定义同时给函数取一个名字,但是有时候我们仅仅需要一个临时函数作为参数或者仅仅只会在某些地方调用一次,这个时候就可以使用匿名函数。lambda表达式的形式与defun类似,它的使用规则如下(lambda (args) "document string" body)除了关键字变了,就是不用写函数名称了。我们使用 funcall 来调用一个lambda表达式(funcall (lambda (name) (message "hello, %s" name)) "emacs")我们执行它将会在mini-buffer中看到显示的字符串信息另外我们也可以将一个lambda表达式赋值给一个变量,最后通过funcall 来调用(setq say-hello (lambda (name) (message "hello, %s" name))) (funcall say-hello "emacs") (say-hello "emacs") ;; error, void-function say-hello知乎的大牛指出,这里原本有一个错误,在这里更正:defun 会把函数值绑定到符号的 function-cell 上,setq 会绑定到符号的 value-cell 上上述的说法,我查过 funcall 、value-cell 以及 function-cell 相关的文档,里面涉及到的一些知识点比较复杂,目前我还没有完全搞明白,而且把它贴出来作为入门来讲有点过于复杂了。变量作用域elisp默认使用 setq 定义的变量不管是在函数内还是函数外全都是全局变量,它们的作用域是全局作用域,例如(defun say-hello () (setq name "Emacs") (message "hello, %s" name)) (say-hello) ;;需要执行一下函数解释器才能执行到定义`name`变量的位置 (message name)一般来说代码如果都是全局变量的话,会给代码的编写和维护带来很大的不便。elisp中同样支持定义局部变量,我们可以使用 let 和 let* 它们的用法类似(let (bindings) body)其中的 bindings 可以是单个值,也可以是括号包裹的键值对。如果是单个值,则默认赋值nil,也就是空。如果是键值对,则将值赋值给对应的键。let定义的变量只能作用在let语句块内,例如下面一个计算圆面积的函数,这里知乎的大牛告诉我,pi 是emacs中的内置变量,我采用PI来定义圆周率(defun circle-area (radix) (let ((PI 3.1415926) area) (setq area (* PI radix radix)) (message "半径为 %.2f 的圆的面积是 %.2f" radix area))) (circle-area 3) (message "%f" area) ;; error void-variable area其中我们在let语句块中定义了两个变量,pi初始化为3.1415926,area 初始化为 nil同样的,可以使用 let* 改写上面的程序(defun circle-area (radix) (let* ((PI 3.1415926) area) (setq area (* PI radix radix)) (message "半径为 %.2f 的圆的面积是 %.2f" radix area))) (circle-area 3)let* 与 let 的区别在于,let* 可以在binding时候使用前面已经定义过的变量。例如上面的代码可以改写成(defun circle-area (radix) (let* ((PI 3.1415926) (area (* PI radix radix))) (message "半径为 %.2f 的圆的面积是 %.2f" radix area))) (circle-area 3)我们使用 let 来改写一下这个程序,发现它会报错(defun circle-area (radix) (let ((PI 3.1415926) (area (* PI radix radix))) (message "半径为 %.2f 的圆的面积是 %.2f" radix area))) (circle-area 3) ;; error void-variable PI好了本篇也该结束了,本篇主要了解了基本的elisp语法,下面进行一下总结:使用 defvar 和 setq 来定义全局变量,其中defvar可以给变量设置一个说明文档,我们使用 C-h v 来查看这个文档使用 let 和 let 来定义变量,其中 let 可以在变量定义的时候使用前面定义过的变量使用 defun 来定义函数使用lambda 来定义一个lambda表达式,使用funcall 来调用lambda,lambda可以赋值给变量,后续使用funcall来调用
2024年12月11日
61 阅读
1 评论
1 点赞
1
...
10
11