Emacs 折腾日记(十)——elisp符号

news/2025/1/20 15:38:32/文章来源:https://www.cnblogs.com/lanuage/p/18681555

符号是有名字的对象,这么说可能有点抽象。我们先来回忆一下C/C++中关于符号的内容。

C/C++ 最终被编译成机器码直接执行,在机器码中不存在变量名称,函数名称等字符,它只有一串地址。但是在写C/C++代码的时候有变量名,函数名,类名,对象名等等名称。编译器是如何做到将符号和地址关联起来的呢?答案是,编译器在编译阶段会提供一个符号表,符号表如何实现的我也不太清楚,但是它做到了关联一个地址和字符串符号的作用。在Windows平台的 vs下,debug版本一般会生成与exe同名的pdb文件,这个是调试文件,它里面保存了一些调试信息,包括符号表。这里的符号与C/C++中的符号表中的符号类似,可以通过它来找到具体的变量。可以理解成elisp提供了这么一种操作符号表的功能。

首先必须知道的是符号的命名规则。符号名字可以含有任何字符。与之对应的,一般的编程语言在定义变量的时候有些特殊符号不能用,而且不能以数字开头,有些关键字也不能作为变量名。而elisp中没有这些限制,只是在使用特殊符号的时候需要使用转义字符。

(symbolp '+1) ;; ==> nil
(symbolp '\+1) ;; ==> t
(symbol-name '+1) ;; error
(symbol-name '\+1) ;; ==> "+1"

上面的代码 symbolp 是在判断一个对象是否是一个符号的函数,symbol-name 用于取符号的名称,它会返回给定符号的名称的字符串。上面的代码说明了,elisp中符号没有什么特别要求,只是对于特定的字符需要使用转义字符。

与c/c++中类似,elisp中的符号名也是区分大小写的。

符号创建

在C/C++中符号表由编译器来创建和操作。而elisp中则提供了操作符号表的方式。符号名要有唯一性,所以一定会有一个表与名字关联,这个表在 elisp 里称为 obarray。从这个名字可以看出这个表是用数组类型,事实上它是一个向量。对于一个新的符号,解释器会首先取字符串的hash值,根据hash值来放入数组对应的位置。同时我们也将这种保存符号的数据结构称之为obarray。也就是说obarray不仅是一个保存符号的变量,也是一种结构。我们也可以在符号上建立这么一个结构用来保存符号对应的属性。也可以作为参数传入emacs 相关函数中

当 elisp 读入一个符号时,通常会先查找这个符号是否在 obarray 里出现过,如果没有则会把这个符号加入到 obarray 里。这样查找并加入一个符号的过程称为是 intern。intern 函数可以查找或加入一个名字到 obarray 里,返回对应的符号。默认是全局的obarray,也可以指定一个 obarray。intern-soft 与 intern 不同的是,当名字不在 obarray 里时,intern-soft 会返回 nil,而 intern 会加入到 obarray里。

(setq foo (make-vector 10 0)) ;; ==> [0 0 0 0 0 0 0 0 0 0]
(intern-soft "abc" foo) ;; ==> nil
(intern "abc" foo) ;; ==> abc
(intern-soft "abc") ;; ==> abc
(intern-soft "abc") ;; ==> nil

lisp 每读入一个符号都会 intern 到 obarray 里,如果想避免,可以用在符号名前加上 #:

(intern-soft "abc") ;; ==> nil
'abc
(intern-soft "abc") ;; ==> abc
'#:abcd ;; ==> abcd
(intern-soft "abcd") ;; ==> nil

可以使用 untern 从obarray中删除对应的符号,如果成功删除,会返回t,如果没有对应的符号,则会返回 nil

(intern "abc") ;; ==> abc
(intern-soft "abc") ;; ==> abc
(unintern "abc") ;; ==> t
(intern-soft "abc") ;; ==> nil
(setq foo 1)
(intern-soft "foo")
(unintern "foo")
(intern-soft "foo")
(1+ foo) ;; error

通过setq,我们让elisp将foo这个变量放入到obarray中,后续使用unintern 删除这个变量后再使用foo的时候就会报错,foo是一个空变量

与hash-table一样,obarray 也提供一个mapatom 函数来遍历整个obarray,例如下面是一个计算所有符号数量的例子

(setq count 0)
(defun count-sys(s)(setq count (1+ count)))(mapatoms 'count-sys)
count ;; ==> 95733
(length obarray) ;; ==> 15121

这里我们看到,数组的长度小于符号的数量。这根hash-table的实现是一样的。各位读者在学习hash-table的时候应该了解过,hash-table 中 hash值不同的元素存储在数组的不同位置,相同的元素通过链表进行串联,一般的hash-table在内存中的结构如下图.

符号的组成

在计算机中,所有的内容都是使用二进制来进行存储的,我们人为的将二进制数据划分为代码和数据。如果单纯的给出一个内存的地址,如何知道它是数据还是代码呢?例如在C/C++中定义了一个int类型的变量a,为什么在后续使用a(1) 这样的语句会报错呢?又例如一个函数指针 pfn,使用 *pfn = 1 这样也会报错呢?编译器怎么知道哪个地址存的是变量,哪个地址存的是函数指针呢?还是通过符号表来解决,符号表中针对每个符号名称都会给定它的类型,例如这个符号对应的地址是一个整数,或者指针,又或者是函数指针。

符号名称

类比C/C++ 中的符号表,elisp中每个符号都可以有4个组成部分,一个是符号名称,它可以类比到符号表中的名称,可以用symbol-name 来访问。它返回一个符号的字符串名称,关于使用的例子在最开始已经给出了。

符号值

第二个组成部分是符号值,可以类比成普通变量的值,可以通过set函数来设置,通过 symbol-value 来获取。

(set 'abc "i am abc") ;; ==> "i am abc"
(symbol-value 'abc) ;; ==> "i am abc"
abc ;; ==> "i am abc"

set 以及 symbol-value 需要提供一个符号,表示对哪个符号进行操作。解释器执行第一行代码的时候未发现 abc 这个符号,那么它会将abc放入符号表中,然后这个符号就可以作为普通变量来使用了。最后一行代码我们直接将它作为普通变量那样使用,直接对它进行求值,发现它也可以获取到具体的值

我们使用 setq 也可以达到这样的效果

(setq val 123) ;; ==> 123
(symbol-value 'val) ;; ==> 123
val ;; ==>123(set 'val 1234) ;; ==> 1234
(symbol-value 'val) ;; ==> 1234
val ;; ==> 1234

从上面的代码中发现,setq 直接使用变量名来对变量进行赋值,而set 则需要对符号进行quote操作。我们可以将setq 看做是一个宏(至于宏是什么,会在后面进行介绍),也就是 set quote,自动将后面的符号进行quote操作。

但是setq只能对全局的obarray中的符号进行赋值,如果我们想放到指定的obarray中进行,此时就不能使用setq

(setq foo (make-vector 10 0))
(set (intern "value" foo) 123)
(symbol-value 'value) ;; ==> error
(symbol-value (intern-soft "value" foo)) ;; ==> 123

setsymbol-value 没有直接的参数来指定符号所在的obarray,如果想要使用自定义的obarray,那么就需要借助 internintern-soft、这样可以指定obarray 的函数来进行辅助操作。

如果一个符号的值已经有设置过的话,则 boundp 测试返回 t,否则为 nil。对于 boundp 测试返回 nil 的符号,使用符号的值会引起一个变量值为 void 的错误

(intern "null")
(boundp 'null) ;; ==> nil
null ;; error
(set 'null 123)
(boundp 'null) ;; ==> t
null ;;==> 123

函数

第三个组成部分是函数,它可以类比成函数指针, 它可以用 symbol-function 来访问,用 fset 来设置。在之前一篇文章中,有知乎大牛指出我的问题,根据大牛的描述,在绑定lambda表达式时将函数部分绑定到符号的函数部分,使用funcall 调用的时候是在取函数部分的内容执行。这里详细了解符号相关的知识之后上述表达就很容易理解了。

(setq foo (make-vector 10 0))
(fset (intern "abc" foo) (lambda (name)message "hello,%s" name))(funcall (intern-soft "abc" foo) "Emacs") ;; ==> error

上述的代码会报告一个错误,因为这里我们使用的obarray 是自定义的foo,它里面没有message这个符号,当然我们可以使用 intern来获取全局的 message 函数,并将它放入到foo中。这里的代码可以这么改

(fset (intern "abc" foo) (lambda (name)(funcall (intern-soft "message") "hello,%s" name)))(funcall (intern-soft "abc" foo) "Emacs") ;; ==> "hello,Emacs"

类似的,可以用 fboundp 测试一个符号的函数部分是否有设置。

(fboundp 'message)
(fboundp (intern-soft "message" foo)) ;; ==> nil(fset (intern "message" foo) (symbol-function 'message))
(fboundp (intern-soft "message" foo)) ;; ==>t

属性列表

第4个组成部分是属性列表,关于这部分我暂时还没想到该怎么用C/C++进行类比,如果非要一个类比的话,可以用这个类比。

C/C++的编译器在看待变量的时候是将变量转变成对应的内存地址,操作变量实际上就是在操作变量所对应的内存。从CPU的角度来讲,CPU并没有规定哪些内存是只读的,哪些是数据,哪些是代码。编译器是如何做的呢?答案应该是编译器会在符号表中对各个符号做一些标记,例如const型变量所对应的内存不能修改。具体编译器是如何实现我也不太清楚,先这么生搬硬套吧,至少在了解elisp的符号这块,这么理解可能会稍微具体一点

elisp中的属性列表,用于存储和符号相关的信息,比如变量和函数的文档,定义的文件名和位置,语法类型。属性名和值可以是任意的 lisp 对象,但是通常名字是符号,可以用 get 和 put 来访问和修改属性值,用 symbol-plist 得到所有的属性列表:

(put (intern "abc" foo) 'doc "this is abc")
(get (intern-soft "abc" foo) 'doc) ;; ==> "this is abc"
(symbol-plist (intern-soft "abc" foo)) ;; ==> (doc "this is abc")

符号的属性列表在内部表示上是用(prop1 value1 prop2 value2 ...) 的形式, 在存取上有点像C/C++中的map,但是在elisp中并不是所谓的map结构。

另外还可以用 plist-get 和 plist-put 的方法来访问和设置属性列表,在上一段代码的基础之上(也就是设置了符号 abc 的 doc 属性的前提下),使用如下代码来进行测试

(plist-get (symbol-plist (intern-soft "abc" foo)) 'doc) ;; ==> "this is abc"
(plist-put (symbol-plist (intern-soft "abc" foo)) 'foo 69)
(get (intern-soft "abc" foo) 'foo) ;; ==> 69
(setq my-plist '(doc "this is abc")) ;; ==> "this is abc"
(plist-put my-plist 'foo 89)
(plist-get my-plist 'doc) ;; ==> "this is abc"
(plist-get my-plist 'foo)
(get (intern-soft "abc" foo) 'foo) ;; ==> 69

从上面的代码来看,plist-get 和 plist-put 需要一个额外的属性列表的操作表示要操作的属性列表,但是它也可以通过传入符号的真实属性列表直接来操作符号的属性列表。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/872185.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何通过跨境电商流程管理工具提升项目效率?几个工具推荐

在竞争激烈的跨境电商领域,合适的工具就如同得力的助手,能够显著提升运营效率、优化业务流程,助力企业在全球市场中脱颖而出。从市场调研、选品采购,到店铺运营、营销推广以及物流管理等各个环节,都有相应的专业工具可供选择。接下来,我们将深入探讨一系列跨境电商实用工…

uniapp上传文件

1.h5不支持设置请求头,所有h5通过上述方式上传失败 2.如果前端不传图片需要用两种方式分别调用,且需要设置不同的请求头,后端需要同步更改 来源:https://blog.csdn.net/qq_23859799/article/details/126794172

2025.1.20 CSS基础

1、语法: 选择器{ 属性:值; 属性:值; ... } 例如: <html> <head> <meta charset="utf-8"> <style> p {color:red;text-align:center; } </style> </head><body> <p>Hello World!</p> <p>这个段…

小乌龟 svn 批量解锁方式

再次点击 get lock ,弹出来的框里面勾上“steal the lock” 强制获取锁 ok 之后,再来释放锁: 即可。

git密码带有特殊字符转义

git clone -b dev http://admin:%21%40%23%24%^abc%2C.%2F@xxx.gateway

Vim 操作

目录vi/vim 的使用命令模式输入模式底线命令模式vi/vim 按键说明移动光标的方法搜索替换删除、复制与贴上进入输入或取代的编辑模式快速移动光标在当前行上移动光标跨行移动光标翻页操作指令行的储存、离开等指令vim 环境的变更分屏操作vi/vim 的使用 VIM 常用的有四个模式:正…

NS3环境配置:NetAnim工具:通用安装

在NS3中需要使用进行网络可视化,其中工具NetAnim是官网源代码中所带工具: 编译NetAnim工具:cd netanim-3.109/ make clean qmake NetAnim.pro make 运行Netanim工具:cd netanim-3.109/ ./NetAnim 以上步骤通用于所有Netanim版本的安装

超大体积文本的快速打开查询EmEditor

前言 无法用grep精确查找日志内容的情况下,我会选择将日志文件拉到本地用文本编辑器打开查找关键信息 但有时候需要查看上百MB的日志文件,我们用常规的VsCode或者记事本打开容易出现卡死的问题 (据说win11的记事本倒是解决了这个问题) EmEditor 推荐使用EmEditor这个软件,…

请使用纯js实现一个横向或纵向的无缝滚动效果

以下是一个使用纯JavaScript实现的简单无缝滚动效果的示例。这个例子是横向滚动的,但你可以通过修改CSS和JavaScript中的某些部分来轻松实现纵向滚动。 HTML: <div id="scrollContainer"><div id="scrollContent"><img src="image1.j…

注解开发 -2025/1/19

纯注解开发bean管理 知识点1:@Autowired名称 @Autowired类型 属性注解 或 方法注解(了解) 或 方法形参注解(了解)位置 属性定义上方 或 标准set方法上方 或 类set方法上方 或 方法形参前面作用 为引用类型属性设置值属性 required:true/false,定义该属性是否允…

CTFshow-Web入门模块-爆破-web23

CTFshow-Web入门模块-爆破-web23题目源码 php代码爆破 <?php/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-03 11:43:51 # @Last Modified by: h1xa # @Last Modified time: 2020-09-03 11:56:11 # @email: h1xa@ctfer.com # @link: https://ctfer.com…

prometheus安装及使用

Prometheus 架构图环境准备: 10.0.0.31 prometheus-server31 2 core 2GB+10.0.0.32 prometheus-server32 1 core 1GB+10.0.0.33 prometheus-server331 core 1GB+10.0.0.41 node-exporter41 1 core 1GB+10.0.0.42 node-exporter42 1 core 1GB+10.0.0.43 node-exporter431 core 1…