emacs lisp基本
Table of Contents
1. 变量声明与函数定义
1.1. 通过setq声明变量
setq是声明变量的关键词,用法如下:
(setq foo "bushiwo") (message foo)
输出结果是:
"bushiwo"
message函数起到了将变量输出到缓冲区的作用.
1.2. 通过defvar定义不会改变声明的变量
用法和setq一致,但是定义的变量数值不会发生改变.如果定义的变量已经被定义,那么赋值将会失败. 比如说:
;; 在前面已经通过setq定义了foo的前提下 (defvar foo "liangzi" "This value will say who is a foo.") ;; 这一行是对document进行解释.
当我们使用message对其进行打印时,其输出结果同样是"bushiwo".
1.3. 通过defun定义函数.
比如,我想定义一个求圆的面积的函数,我可以这么去写:
;; 首先,声明PI的值 (setq pi 3.14) ;;;3.14 (defun get-circle-sq (rr) "这是一个求圆的面积的函数" ;;这是一行注释,表明这个函数在干什么 (message "圆的面积是%.2f" (* pi (* rr rr))) (* pi (* rr rr)) ) ;;;get-circle-sq (setq r 2) ;;;2 (get-circle-sq r) ;;;12.56 (setq cc (get-circle-sq r)) ;;;12.56
1.4. 使用局部作用域
对于刚刚的函数,我们把pi定义在了函数外面,这样并不好.并且,在函数内部我们没有定义任何的局部变量.这在一个较长的函数里面是难以实现的.下面就介绍如何使用let进行局部作用域的定义.并在此基础上,考虑局部变量的声明问题.
(defun get-circle-sq (r) (let ( (pi 3.1415926) sq ) (setq sq (* pi (* r r))) (message "the sq of [%.2f]r is %.2f" r sq) ) sq) ;;;get-circle-sq (get-circle-sq 2) ;;;12.56
当然,也可以用let* 让表达式更加精简,比如:
(defun circle-area (radix) (let* ((pi 3.1415926) (area (* pi radix radix))) (message "直径为 %.2f 的圆面积是 %.2f" radix area)))
1.5. 在对函数和变量观察时的技巧(使用C-h)
我们可以通过C-h f来查询一个函数相关的信息,或者通过C-h v来擦汗寻一个变量的信息.
1.6. 匿名函数表达式lambda
lambda表达式的格式和寻常的函数定义比较相似(只不过它没有名字而已.) 下面是一个简单的例子:
(lambda (r) "find the sq of r" (* r r) )
在对函数进行调用时,可以简单地使用funcall. 下面是一个例子:
(funcall (lambda (r) (* r r)) 2)
或者把它赋值给一个变量:
(setq sq (lambda (r) (* r r))) (funcall sq 2 )
除此之外, lambda主要被用于作为参数在函数之间进行传递.这也是函数式编程的主打卖点吧.
2. 基本逻辑顺序
下面对每门语言基本存在的逻辑顺序进行简单的介绍.
2.1. 顺序执行
顺序执行在函数里是默认的.这毫无疑问,也毫无问题.显式地确定顺序执行的方法是使用progn,如:
(progn (setq a 3) (setq b 4) (message "b-a=%d" (- b a))) ;;;b-a=1
2.2. if 与cond
if大家都比较熟悉,cond就是类似于switch-case的结构.下面以一个例子作为介绍:
(defun myabs (x) (if (> x 0) x (* -1 x))) (myabs -1) ;;;1 (defun good-abs(x) (cond ((> x 0) x) ((< x 0) (- 0 x)) ((= x 0) 0) )) (good-abs -3) ;;;3
2.3. while 控制循环
事实证明,这个while循环很危险,经历一次死机之后,此处的所有文档全部消失了.
(defun factorial (n) (let ((res 1)) (while (> n 1) (setq res (* res n) n (- n 1))) res)) (factorial 10)
2.4. 逻辑运算 and or not
不消多说,直接上例子:
(defun get-rectangle-sq (a &optional b) (or b (setq b a)) (if (= a b) (progn (message "这是一个正方形") (* a a) ) (progn (* a b)) ) ) (get-rectangle-sq 2) ;;;4 (get-rectangle-sq 2 3) ;;;6
3. 数据结构
3.1. 数字
下面简单列一下数字相关的函数,详情可以来这里学习.
;; 测试函数 (integerp OBJECT) (floatp OBJECT) (numberp OBJECT) (zerop NUMBER) (wholenump OBJECT) ;; 比较函数 (> NUM1 NUM2) (< NUM1 NUM2) (>= NUM1 NUM2) (<= NUM1 NUM2) (= NUM1 NUM2) (eql OBJ1 OBJ2) (/= NUM1 NUM2) ;; 转换函数 (float ARG) (truncate ARG &optional DIVISOR) (floor ARG &optional DIVISOR) (ceiling ARG &optional DIVISOR) (round ARG &optional DIVISOR) ;; 运算 (+ &rest NUMBERS-OR-MARKERS) (- &optional NUMBER-OR-MARKER &rest MORE-NUMBERS-OR-MARKERS) (* &rest NUMBERS-OR-MARKERS) (/ DIVIDEND DIVISOR &rest DIVISORS) (1+ NUMBER) (1- NUMBER) (abs ARG) (% X Y) (mod X Y) (sin ARG) (cos ARG) (tan ARG) (asin ARG) (acos ARG) (atan Y &optional X) (sqrt ARG) (exp ARG) (expt ARG1 ARG2) (log ARG &optional BASE) (log10 ARG) (logb ARG) ;; 随机数 (random &optional N)
3.2. 字符与字符串
3.2.1. 字符的表示
elisp里面的字符和C一样,都是用单引号表示,同时与一个整型数字挂钩(比如ASCII值). 此处给出一个简单的示例:
;; 'A' ?A ;;-> 65
除此之外, 对于一些自身包含意义的符号, 可以使用转义字符进行表达. 比如, 对于( 可以用?\(进行表达.
除此之外引申出来的就是一些控制字符, 如:
?\a => 7 ; control-g, `C-g' ?\b => 8 ; backspace, <BS>, `C-h' ?\t => 9 ; tab, <TAB>, `C-i' ?\n => 10 ; newline, `C-j' ?\v => 11 ; vertical tab, `C-k' ?\f => 12 ; formfeed character, `C-l' ?\r => 13 ; carriage return, <RET>, `C-m' ?\e => 27 ; escape character, <ESC>, `C-[' ?\s => 32 ; space character, <SPC> ?\\ => 92 ; backslash character, `\' ?\d => 127 ; delete character, <DEL>
另一种更寻常的控制字符的生成格式是:
?\^I ?\^i ?\C-I ?\C-i
类比之,Meta键的控制为?\M-\C-b这种风格.
3.2.2. 由字符产生字符串的一些函数
这部分的内容还挺无聊的,总结一下就是:
- 由重复的字符构造字符串 make-string
- 连接若干个字符构成字符串 string
- 从一个字符串中按照索引提取一部分字符 substring
- 字符转成字符串 char-to-string
- 整型数字转成字符串 number-to-string
- 连接字符形成字符串 concat
- 全部小写化 downcase
- 全部大写化 upcase
- 首字母大写 capitalize, upcase-initials
3.2.3. 对字符串的简单处理,查找和替换
- 查找
下面是进行查找的一个示例:
(string-match "34" "01234567890123456789") ; => 3 (string-match "34" "01234567890123456789" 10) ; => 13
在这个例子中,"34"作为一个被匹配的字符串,函数将会返回该字符串匹配的第一个索引,当然,也可以使用数字来确定从哪里开始搜索,比如下面的那行所确定. 实际上,string-match所接受的被查找参数是可以出现字符串的.
在此函数的基础上,可以构造出一个连续查找的函数,如下所示:
(let ((start 0)) (while (string-match "34" "01234567890123456789" start) (princ (format "find at %d\n" (match-beginning 0))) (setq start (match-end 0))))
其中,match-beginning与match-end都是常见的两个东西.
- 替换
替换的功能需要稍微依据查找来实现,比如:
(let ((str "01234567890123456789")) (string-match "34" str) (princ (replace-match "x" nil nil str 0)) (princ "\n") (princ str))
其中,replace-match函数实现了复现的效果.
3.3. cons cell and list
3.3.1. cons cell
cons cell 就是一个二元组. 这个二元组主要可以分成两部分,(CAR CDR). 一般而言, 初始化一个cons cell的基本语法是:
'(1 . 2) ; => (1 . 2) '(?a . 1) ; => (97 . 1) '(1 . "a") ; => (1 . "a") '(1 . nil) ; => (1) '(nil . nil) ; => (nil)
可以看出,任何不同类型的两个东西都可以通过这种方式汇聚在一起. 所以说, cons cell本质上是通过一种指针式的索引构造出来的. 这也是cell的由来.
有一个问题,就是括号前面的单引号. 我们为什么要在括号前面放一个单引号呢? 是这样的, 我们知道在elisp的语法里括号里面的第一项都是一个可以被执行的函数, 而我们构造的cons cell 显然不是这种情况. 所以, 我们就使用一个单引号来将之符号化, 所谓的符号化就是指的"引用化", 也就是暂时不对其进行解析. 这个单引号的作用近似于(quote (1 . 2)).
通过这样的定义,我们对一个cons cell的输出显然也就是其输入了.
3.3.2. list
list常常是指由cons cell生成的一些东西. 我们来看一些特殊的list吧.
- 空列表. 就是nil,或者'(). 显然空列表不是cons cell,因为没有CAR和CDR. 但是我们如果用CAR与CDR对其进行访问,都可以得到nil,所以我们认为它是conscell未尝不可.
- true list. 对一个list无数求cdr,最后得到的是nil.
- dotted list. 求cdr的结果是一个非cons cell的东西.
- circular list. 环形列表.