首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
88 阅读
2
nvim番外之将配置的插件管理器更新为lazy
73 阅读
3
2018总结与2019规划
55 阅读
4
PDF标准详解(五)——图形状态
37 阅读
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
累计撰写
311
篇文章
累计收到
27
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE 运动
菜谱
页面
归档
友情链接
关于
搜索到
311
篇与
的结果
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日
8 阅读
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日
11 阅读
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日
11 阅读
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日
6 阅读
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-07
辣椒炒肉
先准备材料准备适量肥肉切片,可以是五花肉准备适量瘦肉,可以选择猪前腿肉根据口味不同,喜欢肥肉的可以肥瘦55开,或者瘦肉多余肥肉准备几个螺丝椒,斜切成带尖的块准备几个蒜瓣,用刀拍扁将瘦肉使用料酒、少许盐、鸡精、老抽、生粉进行腌制,腌制两分钟然后起锅烧火锅热之后放入辣椒,加入少许盐,用铲子压一压,待辣椒呈虎皮状时盛出锅热后加入少许油,然后倒入肥肉,煸出多余油脂,这个时候锅里会有一些油接着倒入蒜瓣和瘦肉一起翻炒瘦肉翻炒的差不多熟了之后加入辣椒,然后添加调料:少许盐、耗油、生抽、鸡精,豆豉翻炒一会就可以出锅了
2024年12月07日
12 阅读
0 评论
0 点赞
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日
11 阅读
0 评论
0 点赞
2024-12-05
在wsl2中安装archlinux
在之前的博客中,我介绍了如何在虚拟机或者真实机上安装archlinux并且进行一定的配置,但是实际上Linux不管怎么配置在日常使用中都没有Windows简单便利,在开发有关Linux的程序时过去用虚拟机或者直接在Windows上使用ssh在远程服务器上进行开发。但是微软发布了wsl以及后续更新的wsl2,可以很方便的实现在Windows中拥有两个系统,并且两个系统可以进行互联。在不改变Windows操作习惯的基础之上操作Linux。进一步提升了Linux的便捷易用性。晚上的教程大多数都是使用wsl安装Ubuntu的,作为一个铁archlinux党,我不太用得惯Ubuntu,那么就要想办法安装自己习惯的arch,好在在网上有现成的教程。终于完成了这一工作准备工作安装wsl2,需要Windows 11或者Windows 10 的19041 及更高版本。因此如果系统版本不够需要提前进行系统的更新操作我们需要在Windows功能中开启 "适用于Linux的Windows子系统",如下图所示勾选之后可能需要重启。或者也可以在应用商店进行安装。这里就不得不吐槽一下微软的应用商店了,它居然能做到挂梯子和不挂梯子一样卡。我这里死活登录不上,就不演示这种方式了安装完成之后可以在powershell中查看它的版本wsl -v从图上可以看到,我们已经安装上了wsl2安装 archlinux安装完wsl2 之后,我们可以在GitHub官网上下载最新的ArchWSL。这里我们要下载两个东西,一个是不带online 标识的appx文件以及同名的cer文件。下载完成之后双击 .cer 文件,点击“安装证书”,选择“本地计算机”,在下一个页面中选择“将所有的证书都放入下列存储”,点击“浏览”,选择“受信任的根证书颁发机构”,执行安装。证书安装完成之后,我们双击下载的appx 文件,直接点击安装appx文件安装完成之后,可以使用wsl --list来查看当前wsl中的Linux子系统此时已经有了对应的系统了我们在命令行输入 arch 即可进入archlinux子系统,此时是以root的身份进入的配置arch配置普通用户新系统安装之后的第一件事就是创建一个普通用户,并且永远以这个普通用户进行登录,在需要的时候使用 sudo 来申请某些管理员权限进行操作我们在之前安装archlinux的教程中已经提到过对应的操作方式useradd -m -G wheel -s /bin/bash arch我们创建一个名为 arch 的用户,并指定shell为 bash接下来我们使用passwd arch passwd root来设置root和arch两个用户的密码。因为archlinux中安装的编辑器是vim,所以我们先将vim设置一个名为vi的别名ln -sf /usr/bin/vim /usr/bin/vi然后 使用visudo 将文件中 #%whell ALL=(ALL) ALL 这行的注释去掉,以便当前用户能够使用 sudo 命令接着可以使用su arch 将当前用户切换到arch。并且使用命令sudo pacman -Syyu来更新系统,同时测试一下输入用户密码之后能否执行一些root命令之后我们推出 arch 子系统,在powershell中执行Arch.exe config --default-user arch来指定默认使用arch 来登录系统设置完登录用户之后,在powershell中输入 arch 进入archlinux子系统中,此时我们发现,登录用户已经变成arch了初始化密钥环和更新源接着我们执行以下命令,来初始化密钥环sudo pacman-key --init sudo pacman-key --populate sudo pacman -Syy archlinux-keyring sudo pacman -Su鉴于目前国内的网络环境,上述几条命令有可能执行出问题或者卡着不动,我们可以进行换源的操作,这里我采用清华源在/etc/pacman.d/mirrorlist 文件的最顶端添加Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch开启32位软件库支持与ArchLinuxCN库的支持按照之前博客中的内容,我们先打开 32位软件的支持,在 /etc/pacman.conf 中去掉[multilib]一节中两行的注释,来开启 32 位库支持。然后在该文件的结尾处加入下面的文字,来开启 ArchLinuxCN 源[archlinuxcn] Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch之后通过以下命令安装 archlinuxcn-keyring 包导入 GPG key。pacman -Sy archlinuxcn-keyring具体的使用可以看镜像的官方文档如果报错可以看官网或者看我博客的办法是否能解决上述命令都成功之后使用 sudo pacman -S yay 来安装 yay成功之后我们可以试着安装一下neofetchyay -S neofetch安装成功之后,执行neofetch 就可以看到系统信息了配置终端基础内容安装完成之后,我们可以对终端进行一下美化和配置,毕竟日常使用上大部分时间都是直接在终端中使用,有一个漂亮点的终端用起来也舒服一点我们首先安装一下必要的组件sudo pacman -S net-tools man-db man-pages man-pages-zh_cn texinfo ntfs-3g tree pacman-contrib neofetch wget git usbutils pciutils acpi base-devel接着我们安装一下相应的字体sudo pacman -S adobe-source-han-serif-cn-fonts wqy-zenhei sudo pacman -S noto-fonts-cjk noto-fonts-emoji noto-fonts-extra ## 这里我把官方推荐的所有带unicode标识的全装上了,这样后续就不太会出现乱码的情况了 yay -S ttf-ubraille ttf-symbola otf-cm-unicode ttf-arphic-ukai ttf-arphic-uming ttf-dejavu gnu-free-fonts ttf-google-fonts-git nerd-fonts-complete ttf-hack ttf-joypixels默认的bash 功能比较弱,我们采用zshsudo pacman -S zsh然后将默认的shell 设置为 zshchsh -l # 列出系统中存在的所有shell chsh -s /bin/zsh #根据上一个命令得到的shell路径,设置当前shell我们推出系统再次登录的时候,shell已经切换到zsh了,我们按照提示生成一个默认的 .zshrc 文件即可为了支持终端上显示一些图形,我们需要安装 nerd-font 。可以在GitHub上找到 nerd-font可以通过git clone https://github.com/ryanoasis/nerd-fonts.git --depth=1来克隆,也可以通过在宿主机上下载zip包,然后传入archlinux 中。我们可以直接在Windows的文件资源管理器上输入 \\wsl$ 来访问archlinux的文件系统在获取项目之后,在项目的根目录执行 sh ./install.sh 来执行安装需要注意的是,如果你是通过本地的windows terminal 来ssh到远程archlinux化,本地也需要安装 nerd-font 因为这个时候显示的责任在宿主机的终端程序。需要宿主机本身也有那些字体。Windows上可以使用 .\install.ps1 来安装字体准备好后,我们可以使用 powerlevel10k 来美化终端git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc source ~/.zshrc接着按照它的提示选择样式即可,如果不满意了,可以在 .zshrc 中对应的配置,直接重新选择样式即可。完成之后,可以看到终端相对来说比较好看了接下来我们对zsh 进行一些简单的配置安装 zsh-autosuggestions ,它是一个命令提示插件,当你输入命令时,会自动推测你可能需要输入的命令,按下右键可以快速采用建议git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions在 .zshrc 中添加下列代码source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh安装完成之后效果如下我们可以按TAB自动补全[zsh-syntax-highlighting]() 是一个命令语法校验插件,在输入命令的过程中,若指令不合法,则指令显示为红色,若指令合法就会显示为绿色。git clone https://github.com/zsh-users/zsh-syntax-highlighting.git echo "source ${(q-)PWD}/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >> ${ZDOTDIR:-$HOME}/.zshrc安装完成之后效果如下autojump 是一个懒人神奇,有了它,当我们此前跳转过某个路径之后,可以很方便的跳转到之前路径。例如我们之前跳转到/usr/bin /home/arch那么我们可以直接使用 j b 跳转到 /usr/bin 中,或者在其他目录使用 j a 跳转到家目录中,如果跳转的目录路径有相似的字符,可能需要多输入一些字符来区分我们下面来安装它git clone https://github.com/wting/autojump.git cd autojump ./install.py安装好之后,它会提示我们需要手动将下列命令加入到 /zshrc 中,我们添加就好了thefuck 这也是一个懒人神奇,当命令输入错误的时候只要fuck一下就好了在archlinux中,我们可以直接使用sudo pacman -S thefuck我们将下列代码放入到 .zshrc 中eval $(thefuck --alias)安装完成之后,效果如下使用gui程序在Windows11和Windows 10 的内部版本在19045以上的时候,wsl本身就可以直接在打开Linux的gui程序。这里我们安装一个firefox作为测试例子sudo pacman -S firefox firefox-i18n-zh-cn我们启动firefox,这个时候报错了不要慌,这里我们需要链接wslg的接口套接字到x11sudo rm -r /tmp/.X11-unix ln -s /mnt/wslg/.X11-unix /tmp/.X11-unix可以将这段代码手动添加到 .zshrc 中,以便重启之后仍然能生效if ! [ -S /tmp/.X11-unix/X0 ]; then sudo ln -sf /mnt/wslg/.X11-unix/X0 /tmp/.X11-unix/X0 fi这个时候再打开就能在Windows上看到firefox的界面了还有一个惊喜,那就是我们可以从开始菜单中找到对应的Linux gui程序,并且直接点击就能打开。使用rdp连接除了这种使用方式,我们还可以使用rdp通过Windows的远程连接来进入archlinux子系统。这里我不太推荐这种方式,因为上面那种方式可以直接在Windows上使用linux gui程序,已经特别方便了,而且这种方式需要有桌面环境,需要消耗资源,但是当你的Windows版本不够的时候,可以考虑使用这种方式来使用gui程序这里我们先安装桌面环境sudo pacman -S xfce4 xfce4-goodies这里我采用轻量的xfce4,不管是使用kde 还是gnome,感觉都比较重接着我们下载rdp相关的软件yay -S xrdp xorgxrdp-glamor pulseaudio-module-xrdp根据 arch wiki 的说法,xrdp 仅支持使用XVNC作为后端,所以这里我们需要安装一下 vncsudo pacman -S tigervnc安装完成之后,需要在home目录下,添加一个.xinitrc 文件,这个文件会在rdp新建一个虚拟桌面的时候执行,文件的内容如下unset SESSION_MANAGER unset DBUS_SESSION_BUS_ADDRESS exec dbus-launch startxfce4最后我们启动一个rdp服务sudo systemctl start xrdp.service最后我们就可以使用Windows远程桌面连接上Linux的桌面了好了,配置到这里也就基本结束了,希望各位小伙伴在其中玩的愉快。总体来说Windows的wsl用起来还是很香的,特别是使用wslg服务在Windows中使用Linux的gui程序。它用起来就跟在Windows上开启一个gui程序一样丝滑,可以很好的将Linux程序融入到Windows的工作流中。
2024年12月05日
88 阅读
10 评论
0 点赞
2024-09-18
PDF标准详解(五)——图形状态
在第三节中,我们说到Q/q 这一对操作符是用来保存和还原图形状态的,那个时候只有一个简单的概念,变换矩阵是图形状态的一员,那么什么是图形状态,以及有哪些图形状态呢?本节将要描述这部分的内容图形状态一个PDF应用程序维护内部数据结构称为图形状态,它保存了当前图形控制参数。这些参数定义在全局框架,在全局框架内可执行图形操作符。例如:f(填充)操作符隐式调用当前颜色这个参数,S(描边)操作符调用了当前线框这个参数从图形状态。这个说法比较的官方,我个人的理解就是它保存了画笔画刷,线性等一系列跟画图相关的属性,在调用图形操作符时,直接采用图形状态中的参数来填充画笔画刷等。目前设备无关的图形状态参数主要有下面几个参数类型值CTMarray当前变换矩阵,例如: a b c d e f cmclipping path(internal)当前剪切路径,初始值:CropBoxcolor spaceName or array当前颜色空间,分为 fill color(填充色) & stroke color(描边色),初始值:DeviceGraycolor(various)当前颜色。初始值:blacktext statevarious有9个图形状态参数组成,用于文本显示。line widthnumber线宽。初始值:1.0line capinteger线帽,线2端的样式。初始值:0 J(square butt cap)line joininteger线连接的样式 。初始值:0 j (miter join)miter limitinteger尖角限量。 初始值:10 M (11.5°)dash patternarray and number虚线。初始值:[] 0 dblend modename or array当前混合模式。初始值:Normalsoft maskdictionary or name指定阴影的形状或阴影不透明值用于透明图像模式。初始值:Nonealpha constantnumber透明度。初始值:1.0。 CA(for stroke) ca(fill)alpha sourcebooleanTrue: 由 SMask 指定透明模式 <br/>false:由 CA 或 ca 指定透明值我们目前介绍过的图形状态主要有变换矩阵以及裁剪区域。剩下的会在后面的内容中依次介绍图形对象的状态变更在pdf 1.7 的标准中,有这么一张图这张图描述了图形状态描述对象的变更。图最中间的是页面对象层,包括图形状态、颜色、文本状态和标记对象等等。坐上角的连线表示,图形对象可以通过 m/re 等操作符进入对路径对象的描述,路径对象在描述完毕后可以使用f、s等操作符显示路径并回到页面对象层,或者可以通过W/W* 来进入对裁剪区域对象的描述。其他的连线也是同样的道理。理解了这个图,也就理解了基本的PDF图形操作逻辑,一般想要的绘制的图形未显示或者裁剪区域未发生作用,一般都是进入的状态不对。按照此思路进行查漏补缺一般可以解决问题本节的内容,不涉及到具体的操作符,仅仅是概念的介绍。后续将会陆续介绍上面提到的图形状态
2024年09月18日
37 阅读
2 评论
0 点赞
2024-09-06
PDF标准详解(四)——图形操作符
上一节,我们了解了PDF中cm操作符,它是定义变换矩阵的。同时也了解到re是创建一个矩阵的。上一节也说过,它用来构建一个路径,具体什么是路径,路径有什么作用呢?这些将在本节给出解释图形操作符是用来在pdf中构建内容并输出到相关设备上进行显示的。pdf中我们能看到的内容几乎都是由图形操作构成的。PDF中主要有6中图形操作符:图形状态操作符(Graphics state operator):CTM当前变换矩阵、 current color、 current clipping path。路径构造操作符(Path construction operators):线的轨迹,,各种图形。绘制路径操作符(Path-painting operators):填充, 描边, 或定义一个剪切区域。其他绘图操作符(自我描述图形对象): 图像(image),shading。文本操作符(Text operator):从字体(代表文本字符的字面/版式(TYPE-FACES)的描述)中选择,显示字符字形字符操作,例如前面显示hello,world 用到的Tj 操作符标记内容操作符(Marked-content Operator): Layers这一次我们主要介绍前两个,后面的等后续慢慢介绍路径路径构建操作符路径对象主要由直线、矩形框(re)、3次贝塞尔曲线构成。对于直线来说,我们需要先使用m(moveto) 来将画笔移动到指定位置,然后使用l(lineto) 来表示将画笔移动到某一个点。例如有下面的例子3 0 obj % 页面内容流 << >> stream % 流的开始 400 400 m 100 100 l s endstream % 流结束 endobj这里我们定义了一个从 (400, 400) 到(100, 100) 的直线。在画直线的时候,m只能有一个,作为起点,而l可以有多个,每有一个l都表示从画笔的上一个点画一条直线到新的位置。例如我们可以模拟一个画一个矩形3 0 obj % 页面内容流 << >> stream % 流的开始 400 400 m 100 100 l s 100 100 m 300 100 l 300 300 l 100 300 l 100 100 l S endstream % 流结束 endobj矩形的例子比较简单,这里就不给出了。我们只需要指定起点坐标并且加上长宽最后用re 操作符作为结束符即可构建对于贝塞尔曲线来说,我们需要4个点来画出一条曲线,它们的位置如下图所示我们需要一个起始和结束位置的点,并且加上两个控制点共同组成一条贝塞尔曲线。贝塞尔曲线我们使用c来作为操作符,在构建的时候需要使用m来规定起始位置的坐标,然后再跟上上图p1, p2, p3 的坐标来控制曲线。例如下面的例子3 0 obj % 页面内容流 << >> stream % 流的开始 100 100 m 200 300 300 400 400 200 c S endstream % 流结束 endobj这样我们构建了一条如下图所示的曲线我们对上面出现的操作符做一个总结操作符含义m设置点的起始位置(moveto)l从当前位置构建一条直线到对应位置 (lineto)re构建矩形路径c构建贝塞尔曲线路径显示操作符上述操作符只能构建一个路径,而这个路径究竟该如何显示,用作何种用途,需要另外给出操作,如果仅仅构建路径,那么页面上是不会有任何显示的,例如上述的内容流,我们稍微做一下更改,去掉最后的S 操作符,我们可以发现之前显示的内容现在不显示了3 0 obj % 页面内容流 << >> stream % 流的开始 100 100 m 200 300 300 400 400 200 c % 只构建路径,而不对路径做任何操作,页面不会有路径的内容 endstream % 流结束 endobj想要显示路径,我们需要使用 S 操作符。上面的路径,我们在最后加上S 就能显示出图形了。另外我们可以使用h操作符来构建一个闭合的路径,它是在原来图形的基础之上,使用一条直线将起始点到终点的两个点连接起来构成要给封闭的区间。例如上面使用直线画矩形的例子,我们可以删掉最后一个l 操作符,并使用h 闭合,照样能形成矩形3 0 obj % 页面内容流 << >> stream % 流的开始 400 400 m 100 100 l s 100 100 m 300 100 l 300 300 l 100 300 l h S endstream % 流结束 endobj去掉h 我们将得到一个开口的矩形。这个读者可以自行尝试,这里就不给出结果了。对于上面的贝塞尔曲线的例子3 0 obj % 页面内容流 << >> stream % 流的开始 100 100 m 200 300 300 400 400 200 c h S endstream % 流结束 endobj加上h 之后将得到下面的结果描边与填充操作这里我们采用S对路径勾画出了边框,也就是描边路径,它对应的英文单词是stroke,我们也可以使用f 或者F(fill)来对路径构成的封闭区间进行填充。默认采用黑色进行填充。3 0 obj % 页面内容流 << >> stream % 流的开始 100 100 m 200 300 300 400 400 200 c h f endstream % 流结束 endobj当然也可以提前指定画刷颜色,这个我们在后面介绍颜色空间的时候再介绍如何定义画刷和画笔。另外也可以使用b或者B(both) 来同时进行描边和填充操作。非0缠绕规则和奇偶绕组规则上述图形,我们很明确的仅定义了一个简单的区域,当出现重叠的复杂区域时,该如何进行填充呢?这里有两套不同的填充规则,即非0缠绕规则和奇偶绕组规则。3 0 obj % 页面内容流 << >> stream % 流的开始 100 350 200 200 re %生成矩形左上角坐标 (100, 350) 宽高都是200 120 370 160 160 re f %按照非0缠绕规则 400 350 200 200 re %生成矩形左上角坐标 (400, 350) 宽高都是200 420 370 160 160 re f* %按照奇偶缠绕规则 endstream % 流结束 endobj这里显示的效果如下我们在这里定义了两组矩形,每组有两个矩形路径进行了重叠。第一组采用非0缠绕规则,第二组采用奇偶规则来填充。我们先以这两个图形为例,来说明这两个规则非0规则:初始化环绕数到 0 。从图形中的任意一点 P 向外任意引一条射线。每遇到一条与该线的交叉线,如果射线与路劲的顺时针相交则计数加一,否则计数减一假如环绕数不等于 0 ,则点 P 在多边形内。但是这个方法有局限性 , 不适合相交 , 或者选一条正切的射线 . 因为射线的方向是任意的 , 这个规则简单的选用射线并不碰到这些情况例如上面我们定义了两个矩形,这两个矩形划分出了两个区域,也就是图中A和B所在区域。我们从A区域随意一点往外引一条射线。从图上看,射线与两条路劲相交,并且都是顺时针相交,所以这里的技术是2。同理,B点与一条顺时针路径路径计数是1。这两个区域的计数都不是0,所以他们都需要进行填充,因此它显示的是上图左侧的效果奇偶规则:从区域内某一点向外引一条射线。简单计算与该射线相交线的数量。如果这个数是奇数,则认为点在图形内。根据这个规则,我们看到A点的计数是2,是偶数,Bdian的计数是1,是奇数。按照奇偶规则,B点需要填充,而A点不需要进行填充。所以它显示的是上图右侧的效果我们再看一个例子3 0 obj % 页面内容流 << >> stream % 流的开始 150 50 m 150 250 l 250 50 l 50 150 l 350 150 l h f 550 50 m 550 250 l 650 50 l 450 150 l 750 150 l h f* endstream % 流结束 endobj这里我们画了两个五角星,线条的顺序按照m给出的为起点,每一个l代表笔画移动的一个端点,根据上述给出的值我们可以得到对应路径的环绕方向,具体的分析过程这里就不展开了,有兴趣的小伙伴可以自己尝试着画图分析一下,然后使用pdf阅读软件打开看看效果与预估的是否一样定义裁剪区域我们利用一些操作符来定义一个路径,这些路径可以作为图形显示出来,也可以作为一个裁剪区域,在该区域中的内容显示出来,不在该区域的内容则丢弃。对于给出的路径,我们使用W (非0缠绕)或者 W*(奇偶规则)来定义一个裁剪区域。例如下面有一个例子3 0 obj % 页面内容流 << >> stream % 流的开始 100 100 200 200 re h W %将上述路径设置为裁剪路径 150 150 m 200 200 l S %在裁剪路径中,所以会显示 0 0 m 500 800 l S %只显示裁剪路径中的内容 endstream % 流结束 endobj这里我们定义了一个长宽都为200的矩形,并且使用h 将矩形区域封闭,然后使用W来将矩形内部作为裁剪区域,然后在(150, 150) 的位置画一条直线到 (200, 200) 的位置。这两个点都在矩形内部,所以会显示出来,另外再画一条从 (0, 0) 到 (500, 800) 的线条,因为这条线有一部分在裁剪区域外,一部分在裁剪区域内,所以只会显示一部分线条。最终图形呈现的效果如下总结本文主要介绍PDF中基本的图形操作符。一般构建图形的操作符有3中使用m 定义画笔的起始位置,然后使用 l 来画一条直线或者直接使用 re 操作符来绘制一个矩形还可以使用c 来构建贝塞尔曲线对于构建的路径,可以使用 h 来进行画笔起始位置和终点位置的连线,这个连线一般是一条直线。对于这个路径我们可以使用 S (stroke) 来对路劲进行描边显示或者使用 f(非0缠绕) f*(奇偶规则)进行内容的填充,又或者使用 b(B) 来描边和填充。我们可以使用 W (非0缠绕)或者 W*(奇偶规则) 来将路径作为一个裁剪区域
2024年09月06日
3 阅读
0 评论
0 点赞
1
...
3
4
5
...
32