首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
105 阅读
2
nvim番外之将配置的插件管理器更新为lazy
78 阅读
3
2018总结与2019规划
62 阅读
4
PDF标准详解(五)——图形状态
40 阅读
5
为 MariaDB 配置远程访问权限
33 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
文本编辑器
Java
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
314
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
26
篇与
的结果
2024-12-28
Emacs折腾日记(六)——elisp字符与字符串类型
本文相关的知识点主要来自 elisp 简明教程 后续内容可以直接查看这个教程上一节我们了解了elisp中基础数据类型之一的数字类型,相比于C/C++ 来说elisp的数字类型更少,学习起来可能也更加简单。那么这篇我们来学习另一个数据类型——字符串字符串的基本介绍回忆以下在C/C++中学到的关于字符的知识,字符采用char 来表示,它占一个字节,里面存储的是各个字符的编码。当然针对汉字或者其他东亚文字,一个char 可能表达不了,它会用 2个或者3个字节来表示一个汉字。后来又有unicode字符,和wchar_t 类型。而字符串则是以0结尾的字符数组。C/C++中经常会出现这样的经典考题char* pszStr = "Hello, World"; char szBuf[] = "Hello, World";它们分别占几个字节,它的考点主要有两个,第一就是指针类型存储的就是地址,它与具体的机器结构有关x86机器上占4个字节。第二个考点就是字符串里面藏了一个0作为字符串的结尾char szBuf[] = "Hello\0Word";这样的字符串虽然可以表达出来,但是我们通过 strlen 之类的函数,得到的结果却是5。因为遇到0就结束了。elisp中的字符串与C/C++中最大的不同就是elisp中字符串可以有 0。另外一个不同就是elisp中没有字符类型,字符串中每一个字符都是字符的unicode形式,用C/C++类比就是字符串通过GetAt 之类的函数返回的是字符的unicode整数值。当然严格意义上来说C/C++中的字符类型也是一个整数值。elisp中可以使用?A 这样的形式来表示一个A字符。最终得到的结果就是A的ASCII码 65,我们可以使用之前学到的数字类型检测函数来判断它得到的是不是整数类型(integerp ?A) ; ⇒ t对于一些标点符号或者有歧义的字符,可以使用 \ 进行转义,例如?\' ?\" ?\\对于一些没有歧义的标点符号加不加转义字符没有影响,但是为了美观或者同一或者说为了不增加记忆的负担,标点符号统一使用转义字符。另外,我们可以在字符串中使用10进制、八进制、16进制的形式来表示字符,例如(setq msg "\x68\x65\x6C\x6C\x6F\x2C\x20\x77\x6F\x72\x6C\x64") ; ⇒ "hello, world"使用其他进制的写法如下:十进制:使用 \d + 数字(不常用,主要用十六进制和八进制)。十六进制:使用 \x + 两位十六进制数字。八进制:使用 \0 + 三位八进制数字。字符串函数首先我们可以使用 length 来获取长度,它有点类似与 Python中的len 函数,它不光可以获取字符串类型的长度,还可以获取列表、向量等类型的长度例如(setq msg "hello, world") (length msg) ;; ⇒ 12我们在前面说过,字符串可以带上0,我们来测试一下有0的情况下,得到的长度如何(setq msg "hello\x00world") (length msg); ;; ==> 11因为elisp并不以0作为字符串的结尾,实际上elisp字符串是以向量的形式存储的,向量中每个元素都是一个整数,所以这里返回的仍然是字符向量的大小。我们可以使用 stringp 来测试一个变量是否为字符串。例如(setq msg "hello") (stringp msg) ; ⇒ t (stringp ?A) ; ⇒ nil我们也可以使用 string-or-null-p 函数来检测,顾名思义,它主要用来判断当前变量是否为字符串或者是一个nil(char-or-string-p msg) ; ⇒ t (char-or-string-p ?A) ; ⇒ t (char-or-string-p nil) ; ⇒ nil (char-or-string-p "") ; ⇒ t但是遗憾的是 elisp 中没有判断字符串是否为空的方法,我们只能自己写代码来实现(defun string-emptyp (str) (and (stringp str) (zerop (length str))))这个函数判断当前传入对象是否是字符串,并且字符串长度为0。构造函数可以使用make-string 函数来构造一个字符串,它构造一个里面都是同样字符的字符串,例如(make-string 5 ?A) ; ⇒ "AAAAA"如果想要构造一个不同字符构成的字符串,可以使用 string(string ?A ?B ?C) ; ⇒ "ABC"也可以使用 substring 和 concat 来产生一个新的字符串,前者从字符串中取子串,后者连接两个字符串。substring 接受一个字符串和两个整数,表示一个范围,是一个前开后闭的区间,也就是包含前面的范围不包含后面的范围。字符串的索引也是从0开始。(substring "Hello, World" 3 5) ; ⇒ "lo"也可以只包含一个起始位置,表示从这个位置开始往后的字符(substring "Hello, World" 3) ; ⇒ "lo, World"也可以传入负数,与Python中的索引类似,负数表示从右往左数,但是注意,最右边的字符是-1,因为0表示最左边的数(substring "Hello, World" -5 -3) ; ⇒ "Wo"concat 就相对比较容易理解一些,它就是将两个字符串合并成一个新串(concat "hello" ", world") ; ⇒ "hello, world"与C/C++类似的是,字符串定义之后无法更改,需要更改的话,它的做法是创建一个新的字符串,并且舍弃掉原来的字符串。所以这里将 substring、concat 这种取子串和连接字符串的函数也归类到构造函数中,因为它们的的确确构造了一个新的字符串。字符串比较在C/C++中,比较字符串时使用 strcmp 函数。我们根据它的返回值来决定字符串是大于小于或者等于。在比较的时候从左往右,依次比较它们的编码值,直到遇到不一样的值。它仅仅比较编码值,而不关心字符串长度,只有在前面的字符都相等的时候才会根据长度判断。相信各位在学习C/C++的时候都亲手实现过strcmp函数,这里就不展开了。elisp中字符串的比较函数就比较多了。char-equal 比较两个字符是否相等,默认情况下它会忽略大小写,例如(char-equal ?A ?a) ; ⇒ t如果要大小写敏感的话就不能用这个函数了,那么大小写敏感的时候该怎么比较呢?这个时候千万别犯迷糊,字符本身就是一个整数,完全可以使用 = 或者 eql直接判断(eql ?A 65) ; ⇒ t (= ?A 65); ⇒ t判断字符串是否相等我们可以使用 string= 或者 string-equal 。它们二者是等价的,是同一个函数的不同叫法而已。(setq foo "hello") (setq bar "hello") (string= foo bar) ; ⇒ t使用字典顺序来比较字符串大小使用的是 string< 或者 string-lessp 。与前面类似,它们也是等价的。它的判断逻辑与 strcmp 函数相同。(setq foo "Hello") (setq bar "hello world") (string-lessp foo bar) ; ⇒ t (setq foo "Hello") (setq bar "Hello") (string-lessp foo bar) ; ⇒ nil比较遗憾的是没有 string> 这样的比较。如果想要判断是否大于的话,需要判断不相等并且不小于。根据 string< 的比较逻辑来看,空串是最小的字符串,也就是任意非空字符串都比空串大,因此上面判断是否是空串的代码可以使用这一特性实现(defun string-emptyp (str) (and (stringp str) (not (string< "" str))))不知道各位是否还记得Java String类中有 Equal 函数和 == 来比较字符串。其中Equal来比较字符串内容是否相等,而 == 仅仅比较对象的地址是否相等。elisp中同样有这样的操作,我们使用 eq 来代替 == 判断对象本身是否相等,对于简单类型也就是数字类型,我们使用它来判断数字是否相等,而对于字符串、列表、向量这种复杂类型时,判断它们的地址是否相等。(setq foo ?A) (setq bar ?A) (eq foo bar) ; ⇒ t (setq foo "A") (setq bar "A") (eq foo bar) ; ⇒ nil字符串转化下面来介绍一些字符串和数字类型相互转换的函数我们可以使用 char-to-string 来将一个整数转换成字符串,或者使用 string-to-char 来将字符串转化为整数,当然这个函数只会返回第一个字符的整数值。例如(char-to-string 65) ;; ⇒ "A" (string-to-char "Hello world") ;; ⇒ 72使用string-to-char 只能获取字符串中第一个字符的值,如果我们要取字符串中任意位置的字符该怎么办呢?我们可以使用substring 来获取以对应位置开始的一个子串,然后获取这个子串的第一个字符(defun get-string-char (str index) (string-to-char (substring str index))) (get-string-char "Hello World" 5) ; ⇒ 32或者我们可以也使用 aref 函数,该函数用来取数组中任意位置的值,因为字符串也是一个数组,因此我们可以使用该函数来取字符串中任意位置的字符。例如(aref "Hello World" 5) ;; ==> 32另外我们可以将数字转化为对应的字符串或者将数字字符串转化为对应的整数。它们的功能类似于C/C++ 中的 atoi 和 itoa 函数。string-to-number 用来将字符串转化为数字,它可以支持从2到16进制的转化,例如(string-to-number "ff" 16) ;; ⇒ 255 (string-to-number "A1") ;; ⇒ 0, 默认以10进制进行转化 (string-to-number "10" 2) ;; ⇒ 2number-to-string 用于将数字转化为字符串,它只支持以10进制的形式转化(number-to-string 256) ; ⇒ "256" (number-to-string ?A) ;; ⇒ "65"如果想以任意进制来将数字转化为字符串,那么可以使用 format 函数,它类似于C/C++中sprintf。用来格式化字符串,但是它只支持8进制10进制和16进制的转换(format "%d" 256) ;; ⇒ "256" (format "%#o" 256) ;; => "0400" (format "%#x" 256) ;; ⇒ "0x100"要转化成二进制的话,没有现成的函数可以用,不过我们可以自己实现,相信学过C/C++的应该写过类似的算法,不过我记得当初我学的算法是先入栈再出栈,下面的代码也是采用类似的方式。通过取模最先算出来的在最低位,所以我们在连接字符串的时候将计算的结果放到前面,连接上之前计算的结果(defun number-to-binary (num) (if (= num 0) "0" (let ((binary "")) (while (> num 0) (setq binary (concat (number-to-string (mod num 2)) binary)) (setq num (/ num 2))) binary))) (number-to-binary 256) ;; ⇒ 100000000emacs-lisp 简明教程 中还介绍了其他类型的数据结构与字符串互相转化的函数,这里就不介绍了,等后面学到了再说。另外字符串还有一些大小写转换的函数。使用 downcase 将字符串中的字母都转换为小写字母,使用 upcase 转换为大写字母。例如(downcase "Hello World") ;; ⇒ "hello world" (upcase "Hello World") ;; ⇒ "HELLO WORLD" (downcase "你好,世界") ;; ⇒ "你好,世界" (upcase "αβγ") ;; ⇒ ""ΑΒΓ""这种字母文字它可以进行大小写转换,但是对于中文这种没有大小写字母的就不存在转化了。使用 upcase-initials 来将字符串中每个单词的第一个字符大写,其余的字符它会忽略它。(upcase-initials "hellO woRld") ;; ⇒ "HellO WoRd"函数 captialize 会将字符串每个单词首字母大写,其余的转化成小写(capitalize "hellO wORLD") ;; ⇒ "Hello World"查找与替换字符串最重要的操作还是查找和替换。elisp 中查找主要使用 string-match 。它使用正则表达式来进行查找。elisp中没有C/C++中find 那样查询子串的缩进的函数。查询子串其实也可以利用正则表达式来处理(string-match "Emacs Lisp" "This is Emacs Lisp Program") ;; ⇒ 8该函数的第一个参数是一个正则表达式,上面代码中我们直接使用字符串进行匹配,就是在精准的匹配子串。它返回子串开始的索引。它还可以接收一个数字,表示从字符串的第几个字符开始往后进行查找,这个参数的作用有点像 String.Find(int index) 这个重载函数中 index 的含义。(string-match "Emacs Lisp" "This is Emacs Lisp Program" 10) ;; ⇒ nil但是如果查找的子串中有特殊符号的话,就不能这么使用(string-match "2*" "232*3=696") ;; ⇒ 0这里因为 * 被当成正则表达式的模糊匹配符号,它表示任意一个字符。如果想要它单纯的作为普通符号,可以使用regexp-quote 来处理一下,它的作用是将字符串中的所有特殊字符转义,使其可以安全地作为正则表达式使用。这样可以确保字符串的内容被视为字面量,而不是正则表达式中的元字符。(string-match (regexp-quote "2*") "232*3=696") ; => 2不知道各位读者使用过 C/C++ 中的正则表达式没有,正则表达式匹配之后会产生一个结果对象,它包含了所有匹配上的位置。我们可以通过循环或者其他方式来得到这个位置,并且得到具体匹配上的结果。在elisp中,结果被保存在 match-data 中。它允许你获取最近一次正则表达式匹配的位置信息,包括匹配的起始和结束位置。我个人的感觉它有点像Win32 API中的GetLastError 一样,每次调用其他API结果都会被覆盖,每次调用只能得到上一次的错误码。(progn (string-match "3\\(4\\)" "01234567890123456789") (match-data)) ;; ⇒ (3 5 4 5)我们使用 3(4) 这样的正则表达式来进行匹配。这里括号表示一个捕获组,它匹配34或者单独匹配4。match-data中每对数组表示一个匹配组的起始和结束位置,它是一个前闭后开区间。也就是包括前面的,不包括后面的。上面的代码先匹配 34,发现它在第3个字符处出现,所以它返回第一组数据 (3 5),然后匹配4,它出现在第4个字符,所以产生第二组数据 (4 5)。按照这个思路,如果将正则表达式修改一下改为 3\\(4\\)\\(5\\) 将会产生3组数据,第一组匹配 345 ,第二组匹配 4 ,第三组匹配 5。最后的结果就是 (3 6 4 5 5 6)在上面说到 match-data 中每对数组表示一个匹配组的起始和结束位置。我们可以使用 match-beginning 和 match-end 来获取这两个数据,它需要一个整数作为参数,用来表示取第几组的数据,例如下面的代码(progn (string-match "3\\(4\\)" "01234567890123456789") (message "%d" (match-beginning 0)) ;; => 3 (message "%d" (match-end 0)) ;; ⇒ 5 (message "%d" (match-beginning 1)) ;; ⇒ 4 (message "%d" (match-end 1))) ;; ⇒ 5如果我们给出的参数超过了匹配组的大小,那么它们将会返回 nil ,例如如果上述匹配我们使用 (match-beginning 2) 来取第三组的结果的话,将会得到nil每一个匹配组都是一个半开半闭区间,因为 match-end 是匹配到的字符位置的下一个位置,所以使用它很容易进行循环。上述的代码在匹配的时候,只要是匹配到就停止了,我们可以写一个循环用来继续匹配后面的字符串(let ((start 0)) (while (string-match "34" "01234567890123456789" start) (message "find at %d\n" (match-beginning 0)) (setq start (match-end 0))))掌握了查找的相关操作之后,我们继续来学习有关替换的操作。我们可以使用 replace-match 来将匹配到的字符串替换成指定的字符串,它的函数原型如下(replace-match NEWTEXT &optional FIXEDCASE LITERAL STRING SUBEXP)我们主要需要关注的是 NEWTEXT 它代表的是我们希望用哪个字符串来替换匹配上的字符串,STRING 表示希望进行匹配的字符串,例如(let ((str "hello world 123")) (string-match "\\([0-9]\\)" str) (replace-match "#" nil nil str)) ;; ⇒ "hello world #23"我们也可以使用循环来将所有数字都替换成 #(let ((str "hello world 123") (start 0)) (while (string-match "\\([0-9]\\)" str start) (setq str (replace-match "#" nil nil str)) (setq start (match-end 0))) str) ;; ⇒ "hello world ###"需要注意的是,这里每次执行 replace-match 后都返回一个新的字符串,原来老的字符串保持不变,所以我们这里每次替换之后都手动的使用 setq 来对老的字符串进行赋值,然后再重新匹配。这样才能保证最后的结果是我们想要的结果。我们可以使用下面的代码来验证这一点(let* ((str "hello world 123") (start 0) (final-str str)) (while (string-match "\\([0-9]\\)" str start) (setq final-str (replace-match "#" nil nil str)) (setq start (match-end 0))) final-str) ;; ⇒ "hello world 12#"这里只替换了最后一个数字,这是因为执行 replace-match 之后的str 变量并没有被改变,每次都是之前的再进行匹配,唯一变化的只有 start 的值。最后一次我们仍然在使用原始的 str 字符串在进行匹配,它匹配到 3。所以最后一次替换只将3替换成了 #。我们也可以使用捕获组进行替换,例如我们想将 "hello world 123" 替换成 "123 hello world",那么可以使用如下代码(let ((str "hello world 123")) (string-match "\\(hello world\\) \\([0-9]+\\)" str) (replace-match "\\2 \\1" nil nil str)) ;; ⇒ "123 hello world"这里使用 \\2 \\1 来表示替换的新字符串,它表示的含义是匹配组里面的第二组结果+空格+第一组结果。它们组成了一个新串用来替换原来的字符串。replace-match 的最后一个参数表示替换哪一个捕获组,默认是0,例如上述的代码中,第0个捕获组就是整个字符串,所以它替换了整个字符串,我们可以将上述代码做一下修改(let ((str "hello world 123")) (string-match "\\(hello world\\) \\([0-9]+\\)" str) (replace-match "\\2 \\1" nil nil str 1)) ;; ⇒ "123 hello world 123"它将第一匹配组也就是 hello world 使用新字符串 123 hello world 来替换,然后加上原来剩下的字符串,最终也就得到结果 123 hello world 123到这里,已经将字符串替换的常见操作都做了一些说明。本节的内容也就到此结束了。后面将继续按照 教程来学习。敬请期待!当然了,如果各位读者觉得我的这一系列教程有抄袭的嫌疑,或者质量不如原版,又或者更新缓慢,请按照对应的链接来学习相关内容。
2024年12月28日
10 阅读
0 评论
0 点赞
2024-12-21
Emacs 折腾日记(五)——elisp 数字类型
本文是参考 emacs lisp 简明教程 写的,很多东西都是照搬里面的内容,如果各位读者觉得本文没有这篇教程优秀或者有抄袭嫌疑、又或者觉得我更新比较慢、再或者其他什么原因,请直接阅读上述链接中的教程。上一篇我们讲了elisp中的流程控制结构相关的内容,下面就该进入到对应数据结构的学习了。elisp中主要的数据结构有:整数、浮点数、列表、符号、向量、散列表等等类型。下面我们先从最简单的类型——整数和浮点数说起数字类型与C/C++对比起来,elisp数字类型少的多,C/C++ 整数类型就有好几种,包括有符号、无符号、int、short、long之类的。elisp不区分这些,它仅仅有整数和浮点数。而且elisp 中只有浮点数这一种小数类型,不像C/C++还有双精度浮点数和单精度浮点数之分。elisp的整数范围与具体的机器有关,它的范围可以通过变量 most-positive-fixnum 和 most-negative-fixnum 来得到。例如在我的机器上它们的值如下most-positive-fixnum ;; 2305843009213693951 most-negative-fixnum ;; -2305843009213693952在给变量使用数字类型赋值的时候,我们可以使用10进制或者其他任意进制的形式。例如#b101100 => 44 ; 二进制 #o54 => 44 ; 八进制 #x2c => 44 ; 十进制 #24r1k => 44 ; 二十四进制因为26个英文字母+10个数字的原因,我们最大只能使用36进制来表示一个数字,但是基本不用到这么大的。日常最多也就用用10进制、二进制、16进制、8进制都算用的少。浮点数的表达遵循 IEEE 标准,也就是可以使用带小数点的数字来表示,或者带上 e 来使用科学计数法,例如3.14 1.0e-10数字类型的测试作为动态类型的语言,在代码执行阶段,变量的类型是会发生变化的。我们无法仅通过变量名或者变量的初始化值来判断变量类型。emacs的变量在执行阶段都知道自己的类型,但是它无法主动向我们报告,我们需要使用一些函数来进行判断,关于数字类型,提供了下列的函数integerp floatp numberp从字面上能理解它们分别判断是否是整形、浮点数、以及数字类型。elisp 测试函数一般都是用 p 来结尾,p 是 predicate 的第一个字母。如果函数名是一个单词,通常只是在这个单词后加一个 p,如果是多个单词,一般是加 -p数的比较与C/C++ 类似,数字的比较一般有 >、<、>=、<= 。但是也有不同的地方,因为elisp中都是使用 setq 来进行赋值的,所以它采用 = 来表示数学意义上的相等。还有一个不同的地方因为elisp中没有 += 、-=、/= 、*= 这样的运算符,所以它使用 /= 来作为不等的判断符号与其他语言类似的,浮点数直接使用等于或者不等于来判断并不准确,需要在一定范围内忽略误差。在C/C++中,我们常见的写法是给定一个误差值,然后二者差的绝对值在这个误差值范围内则认为它们相等。我们将这个算法使用elisp改写一下就得到下面的代码(defun approx-equal (x y) (let ((fuzz-factor 1.0e-6)) (< (abs (- x y)) fuzz-factor))) (approx-equal 1.000001 1.00000000000000001) ;; => t上述的写法并不严谨,在一定误差范围内,它是对的,但是在某些情况下它就不对了,例如 1.0e-7 和 1.0e-12 。它们本身并不相等,但是它们都超过了这个误差范围,相减之后的值小于这个误差范围。但是我们看到其实它们直接的差距还是挺大的,间隔1.0e5 的数量积。我们可以将上述算法进行一些改进(defun approx-equal(x y) (let ((fuzz-factor 1.0e-6)) (or (and (= x 0) (= y 0)) (< (/ (abs (- x y)) (max (abs x) (abs y))) fuzz-factor)))) (approx-equal 1.0e-7 1.0e-12) ;; => t这段代码采用的是比较相对差距的办法。因为涉及到除法,所以先把二者等于0的情况排除了,避免发生除0的问题。上述代码改造成对应的C代码就是#define FUZZ_FACTOR 1.0e-6 // 定义误差范围 bool approx_equal(double x, double y) { // 处理特殊情况:如果两个数都是 0 if (x == 0 && y == 0) { return true; } // 计算相对差并进行比较 double relative_difference = fabs(x - y) / fmax(fabs(x), fabs(y)); return relative_difference < FUZZ_FACTOR; }另外 elisp 中有 eql 函数来判断两个数是否相等(eql 1 1.0) ;; => nil (eql 1.0e-7 1.0e-12) ;; => nileql 在判断数字时不光判断值,也判断类型。第一条语句,因为二者类型不同,第二条语句二者都是float属于同类型,但是二者的值不同,因此两个结果都是假。数字的转换elisp 中可以进行 整形和float型数字的相互转换。在C/C++ 中,整形可以通过隐式转换自动转换成float,而float转换成int时会丢失小数位,比如哪怕是 1.9 在转换为整数时也会是 1。在elisp中,可以通过float将整数转化为浮点数。例如(floatp 1) ; ⇒ nil (floatp (float 1)) ; ⇒ t (eql (float 1) 1.0) ; ⇒ t而浮点数转化成整数有下面几个函数truncate: 抹除小数位,也就是C/C++语言中float转int的操作floor: 类似于C/C++ 中的floor 函数,返回小于等于该数的最大整数ceiling: 类似于 C/C++ 中的 ceil 函数,返回大于等于该数的最小整数round: 类似于 C/C++ 中的 round 函数,返回四舍五入后的整数数的运算一般的语言,数的运算无外乎 +、-、*、/ 取整、取模。elisp 中同样有这些操作,前面的加减乘除跟其他语言一致,没什么特别的。C/C++ 以及 elisp 中的除法都不是纯粹数学意义上的除法,它会将结果抹掉小数位转换成整数。我们如果将除数或者被除数转换为float类型的话,那么就得到数学意义上的除法结果 (当然也不全是,毕竟float数据有表达数据的限制)但是python 不一样,它就是纯粹数学意义上的除法。这个设计我也不知道算是好还是不好,毕竟它与其他语言不一致增加了记忆的负担。(/ 3 2) ; ⇒ 1 (/ (float 3) 2) ; ⇒ 1.5 (/ 3.0 2) ; ⇒ 1.5C/C++ 中有 ++ 、 -- 操作,而且还分 前++ 和 后++ 。在 elisp 中没有这两个操作,也没有类似于 += 的操作。elisp的赋值一直是用的 setq。而且它提供了 1+ 1- 这两个符号来表示 ++ 和 --。至于是前 ++ 还是 后++ 呢?两个都不是,C/C++中的 ++ 本身具有改变变量值的作用,它们的区别在于是返回值之前改变还是之后改变。而elisp 主要使用 setq 来改变变量的值, 1+ 这个操作无法改变变量的,它仅仅改变这条语句返回的值。例如可以使用下面的代码来测试(defun inc (num) (1+ num)) (setq foo (inc 3)) ;; ⇒ 4这里将传入的参数加了1,但是其实函数中 num 的值并没有变化,我们可以对函数做一下修改来验证这一点(defun inc (num) (progn (1+ num) num)) (setq foo (inc 3)) ; ⇒ 3要改变变量的值需要使用 setq 来进行赋值,这个函数可以做一下修改(defun inc (num) (progn (setq num (1+ num)) num)) (setq foo (inc 3)) ; ⇒ 4取模的操作,elisp 中提供了两个方式 % 和 mod 函数,其中 % 与其他语言类似,它要求除数与被除数都是整数,而 mod 则没有这个要求。我们查看mod函数,发现它是被写在C代码里面的。它虽然也是取余,但是它与数学意义上取余的结果并不一致,例如(mod -10 3) ;; ⇒ 2 (mod 10 -3) ;; => -2 (% -10 3) ;; ⇒ -1 (% 10 -3) ;; ⇒ 1% 单纯的就是数学意义上的取模的操作,首先找到商,然后根据商来决定模而 mod 则不同,mod 中首先一个原则就是余数和除数的符号相同。所以第一个的结果应该是正数 也就是 -3 * 4 + 2 = 10,余数是2。第二个结果应该是 - 3 * (-4) - 2 = 10 mod 还有一个原则,那就是商的结果应该是整数。利用这两个原则我们就可以大概的还原一下计算的过程(mod 3.5 2) ;; ⇒ 1.5 (mod -3.5 2) ;; ⇒ 0.5 (mod 3.5 -2) ;; ⇒ -0.5根据上面两个原则,那么它们分别可以还原为1 * 2 + 1.5 = 3.5-2 * 2 + 0.5 = -3.5-2 * (-2) + 0.5 = 3.5另外还有一些其他数学上的操作,对于学习后面写配置的话,大多数应该是用不到的。后续需要使用的话再查询就好了,这里就不在多啰嗦了。到此为止我们已经介绍完了elisp中数的常见操作。后续将陆续介绍其他数据类型,敬请期待。
2024年12月21日
12 阅读
0 评论
0 点赞
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日
12 阅读
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日
7 阅读
0 评论
0 点赞
2024-12-07
Emacs 折腾日记(二)——Emacs简单入门
环境 准备这里我们根据之前博客 配置的wsl2+archlinux环境,安装Emacssudo pacman -S emacs如果之前配置的关于gui的部分正确,那么在终端输入 emacs 来启动 或者在Windows的开始菜单中能找到emacs(Arch) 之类的启动项目。当然你也可以使用 emacs -nw 来开始一个终端的emacs程序Emacs 主要界面展示打开之后,映入眼帘的是一个比较丑陋原始的充满历史感的软件,看着都有点让人倒胃口我们能不能将Emacs改造的漂亮一点呢?记住在Emacs上永远不要问能不能,而应该问怎么做。Emacs上没有不可能上面的图中,我将Emacs的各个区域都给标记了一下,很好理解,一般的软件都有这些部分。中间显示的是欢迎信息,欢迎界面中这些链接都是Emacs的帮助信息,都可以点进入看。而下面的状态栏显示了一些基本的信息。最下面的部分我们称之为mini-buffer,会显示一些运行信息我们可以使用q 来退出欢迎界面,进入该界面需要注意下面几个地方软件中间位置已经出现了输入的光标,我们可以在里面输入内容下面有一个 scratch 的字样,它表示当前处于scratch buffer (草稿),顾名思义,它就是一个输入临时内容的地方。它没有绑定任何的文件后面的 Lisp Interaction 和 ElDoc表示当前的mode,会加载一些有关mode的配置,例如lispmode会加载一些有关lisp的高亮等至于状态栏每个字符都代表什么意思,这里就不解释了,后面我们希望对Emacs进行一些美化,美化完成之后就看不到这些信息了。Emacs 的一些概念介绍在学习玩Vim之后,Emacs 的一些概念就变得很好理解了,很多东西都根vim是相似的bufferVim 中的buffer与文件有关联,将来可以保存在文件中,但是Emacs的buffer除了包括vim中buffer相关的内容之外,也有一些其他类型的buffer,目前我知道的有:minibuffer: 界面最下方的一栏,主要显示当前状态和一些其他内容,后续可以对它进行一些定制,可以显示很多有用的信息scratch buffer: 顾名思义,就是草稿箱的概念,用来输入一些临时的内容Message buffer: 用来显示一些信息,例如message 函数的内容modemode,模式。Emacs中一个十分重要的概念,一般来说buffer都会有mode加载在其上,给buffer提供额外的支持。mode 分为两种, major mode 和 minor mode。一般一个buffer只能有一个major mode,但是可以有多个minor mode。每个mode都有一些额外的特征和快捷键绑定,与vim中的文件类型有点类似。不知道小伙伴还记不记得,我们可以针对vim中的文件类型,给每个类型指定额外的快捷键和语法高亮等信息。同样的Emacs的mode也具有这些功能。例如 elisp mode 里面有语法高亮、快速执行lisp代码的一些快捷键。另外也可以做到同样的功能在不同的mode中体现出不一样的效果。相比于vim的文本类型来说,mode的方式更加的灵活,我们可以将一些在不同文件类型中同样的功能抽出来,放到一个mode中,然后在需要的时候以minor mode的形式加载到buffer中。windowwindow的概念与vim中的相同,都是同时显示多个buffer。frame相比vim来说,Emacs多出来了一个frame的概念。上述emacs 的界面就是一个frame,每创建一个frame,就会多出来一个上面的带有标题栏的窗口。相信各位读者也看出来了,只有gui的Emacs可以创建多个frame。一般来说我们使用到window就已经足够了,特别是要进行全键盘操作的时候,多个frame反而是一个负担Emacs 的重要快捷键介绍快捷键之前,我们先来介绍一下Emacs相关的键位。Ctrl:ctrl键,一般简写为CMeta: 一般是Windows键盘布局中的 Alt 键盘,简写为 MSupper: 一般是WIndows键盘布局中的Win 键,简写为S因为vim利用不同的模式来映射快捷键,所以vim能用较少的按键实现很多的功能,而Emacs本身不具备mode的功能,所以它需要很多前缀来将不同的功能进行映射。对于一个vim转Emacs的用户来说,Emacs的快捷键统统不重要,第一,后续肯定会将它改造成vim的按键模式。主要是相比于vim来说,Emacs的文本操作快捷键太长了,没有vim那么简洁。第二,没有人会去特意记忆那么多快捷键,以我使用vim的经验来说,都是慢慢用,慢慢形成肌肉记忆的。作为一个初学者一上来就告诉他,需要记住这些快捷键,很容易就把人吓跑了。但是Emacs毕竟不同与vim,我们还是需要记忆一些特别重要的快捷键快捷键功能C-h f查看函数的帮助信息C-h v查看变量的帮助信息C-f k查看快捷键的帮助信息M-x执行命令前三个主要是查看各种帮助信息,Emacs有一个特点叫自文档,就是它很多东西都自带文档,一切都可以通过查询文档来了解。所以在使用和熟悉的过程中,我们离不开前三个快捷键。它可以互相查,例如忘记了某个功能的快捷键,可以使用 C-h f 输入对应的函数就可以找到它对应的快捷键。或者忘记了某个快捷键绑定到哪个命令了,可以使用 C-h k来查看关于快捷键绑定命令的信息。对于最后一个快捷键,一般来说Emacs的功能都绑定到了一个命令或者说一个函数上,即使我们忘记了快捷键,也可以通过M-x 输入命令来完成操作,有些时候配合自动补全,输入命令不比使用快捷键慢。vim与Emacs的很多地方都是相通的,学习了vim之后,Emacs的很多概念都不需要细说,很快就能上手用了,但是想要用好,用出个性来,还是要对elisp有一定的了解,后面可以会陆续介绍一些elsip的知识。
2024年12月07日
10 阅读
0 评论
1 点赞
2024-12-05
emacs 折腾日记(一)——序言
初次知道emacs这个东西是在《程序员的呐喊》这本书。书中的作者提倡学习编译原理,推崇emacs。现在距离我知道emacs已经过去了快8年,期间不断的重复学习——放弃——学习的路子。与过去学习vim类似,vim我也经历过放弃到学习,最后有项目需要使用Vim在Linux上开发,没办法慢慢学会了它的操作,以及一些简单的配置。后来我写了vim的操作和配置,我发现这样做很有用首先就是在写vim操作的时候,相关的概念和操作技巧我又重新梳理了一遍,加深了它的印象,过去一些不怎么用的操作我发现在之后我用的越来越顺手。其次就是我目前使用的配置在出错或者需要做修改的时候根据之前的文章我又梳理了一遍,很方便我回顾之前的思路能快速定位到每个模块当初的思路以及一些被遗忘的知识点。既然之前的教程对我有这么大的帮助,那么我想这次学习emacs就采用这种策略。首先日常记笔记,记录工作的日志之类的操作就从markdown转化到emacs的org-mode中以便熟悉emacs的日常操作。另外通过写博客的方式记笔记,记录自己做的一些配置以及一些操作技巧。日后用来回顾也好或者仅仅为了加深印象也好都可以。另外我想这一系列既然是折腾日记,而且我还是emacs的小白,那么这一系列的文章自然是不成体系的,基本上是想到什么就写什么。如果看这一系列的文章的读者存在问题我可能也没有能力解答。在这里给读者说一声抱歉。emacs 是什么任何技术或者工具我都喜欢从这几个方面先了解一下,再考虑是不是值得学。首先一个问题就是“是是什么”,其次是“为什么”,最后是“怎么做”。我想现在就来说说我对这几个问题的理解首先Emacs是什么呢?Emacs 其实并不特指某一款编辑器,而是一个文本编辑器大家族的统称。最初由理查德·斯托曼于1975年在MIT协同盖伊·史提尔二世共同完成。这一创意的灵感来源于TECO宏编辑器TECMAC和TMACS。这里我们提到Emacs主要特指 GNU EMacs,它是由理查德·斯托曼于1984年开始开发,至今已经有40多年的历史。它的核心理念官网上是这么介绍的An extensible, customizable, free/libre text editor — and more它是一个 可扩展、可定制、自由的文本编辑器,并且远不止于此。EMacs 是自由软件,它没有对用户做出任何限制,包括内核代码完全是开放的。你可以在此基础之上进行任何改动。同时它提供了一个完成的lisp解释器,随着这些年的发展,它的插件系统已经特别丰富了,利用插件几乎可以实现任何功能。有人戏称Emacs是一个伪装成文本编辑器的操作系统,在Emacs上几乎无所不能。emacs 是否值得学我个人认为Emacs还是很值的学习的。说说我学习Emacs的理由:它是一个与vim齐名的编辑器,因为它的插件丰富,结构设计良好,它几乎可以做到vim能做的所有事情,包括vim那一套操作逻辑它使用lisp作为扩展语言,我目前掌握了面向过程的语言C,面向对象的语言 C++/Python,但是还从来没有完整的学习过函数式编程语言,而且《黑客与画家》的作者也推荐lisp语言。平时写普通的脚本我都是用shell 或者 python 就搞定了,没什么几乎接触到lisp的实战,借这个机会学习一下lisp并且用于实战,也是一个不错的选择emacs 的 orgmode很吸引我,它的功能比markdown要丰富,借助Emacs这个平台能提供非常棒的体验,包括但不限于写博客,做计划,管理日程,甚至能在里面运行代码如何学下面是一张网上流传的图,各个编辑器的难易程度虽然有点夸张,但是Emacs的学习过程并不算简单。目前我也没有好的学习路径,而且这一系列的文章只是我的尝试。目前我也没有掌握Emacs,甚至是一个小白。我想以写文章的方式来慢慢学习关于Emac的内容网上有很多Emacs的教程,最经典应该要数这篇 一年成为Emacs高手但是我之前尝试过使用高手的配置,总是用着不是那么舒服,总想要折腾出一套属于自己的配置,后面慢慢的就放弃了。这次我想慢慢的根据其他人的教程折腾出一套属于自己的配置,然后通过使用 org-mode 来写博客、记笔记、并且进行日程管理,后续慢慢的将写脚本之类的简单开发工作转移到Emacs上,通过折腾配置并且多用Emacs来学习。祝我好运吧。
2024年12月05日
13 阅读
0 评论
0 点赞
1
2
3