2 数据类型
Table of Contents
Lisp 对象是 Lisp 程序使用和操作的一段数据。对于我们的目的,类型或 数据类型 是一组可能的对象:
- 每个对象至少属于一种类型
- 相同类型的对象具有相似的结构,通常可以在相同的上下文中使用
类型可以重叠,对象可以属于两种或多种类型
因此,我们可以询问对象是否属于特定类型,但不能询问对象的类型
Emacs 中内置了一些 基本的对象类型 。这些,所有其他类型的构造,被称为 原始类型 。每个对象都属于一种且仅一种原始类型:
这些类型包括 integer 、 float 、 cons 、 symbol 、 string 、 vector 、 hash-table 、 subr 、 byte-code function 和 record ,以及与 编辑 相关的几种特殊类型,如 缓冲区
请参阅编辑类型
- 每个原始类型都有一个相应的 Lisp 函数,用于 检查 对象是否是该类型的成员
Lisp 与许多其他语言的不同之处在于它的对象是 自类型化 的:每个对象的原始类型都 隐含 在对象本身中
例如,如果一个对象是一个向量,那么没有任何东西可以将它视为一个数字 Lisp 知道它是一个向量,而不是一个数字
在大多数语言中,程序员必须声明每个变量的数据类型,并且类型是编译器知道的,但不会在数据中表示出来
Emacs Lisp 中不存在这样的类型声明。Lisp 变量可以具有任何类型的值,并且它会记住您存储在其中的任何值,类型和所有内容
实际上,少数 Emacs Lisp 变量只能接受某种类型的值。请参阅带限制值的变量
本章描述了 GNU Emacs Lisp 中每种标准类型的用途、打印表示和阅读语法。关于如何使用这些类型的详细信息可以在后面的章节中找到
打印表示和读取语法
- 对象的 打印表示 是 Lisp 函数 print 为该对象生成的 输出格式 ,每种数据类型都有 唯一的 打印表示
- 对象的 读取语法 是 Lisp 函数 read 为该对象接受的 输入格式 。这不一定是唯一的; 许多种类的对象有 不止一种 语法
在大多数情况下,对象的打印表示也是对象的读取语法。然而,有些类型 没有读语法 ,因为在 Lisp 程序中将这些类型的对象 作为常量输入是没有意义的 。这些对象以 散列符号 打印,由字符 #< 、 描述性字符串 (通常是类型名称后跟对象名称)和结束符号 > 组成。例如:
(current-buffer) ⇒ #<buffer objects.texi>
哈希表示法根本无法读取,因此 Lisp 阅读器在遇到 #< 时会发出错误无效读取语法的信号
在其他语言中,表达式是文本; 它没有其他形式
在 Lisp 中, 表达式 主要是 Lisp 对象 ,其次是作为 对象读取语法的文本
很多时候不需要强调这个区别,但是一定要放在脑后,不然偶尔会很迷茫
当以交互方式执行表达式时,Lisp 解释器:
- 首先读取它的文本表示,生成一个 Lisp 对象
- 然后 eval 该对象
但是,eval 和 read 是不同的活动 Read 返回所读文本所代表的 Lisp 对象; 该对象以后可能会或可能不会被 eval 有关读取的描述,请参阅输入函数,读取对象的基本函数
特殊读语法
Emacs Lisp 通过 特殊的散列符号 表示许多特殊的对象和结构:
没有读取语法的对象
‘#<…>’
名称为 空字符串的内部符号 的打印表示形式
‘##’
函数的快捷方式
‘#'’
名称为 foo 的非内部符号的打印表示是 '#:foo'
‘#:’
打印循环结构时,此构造用于表示结构循环回到自身的位置,'N' 是起始列表计数:
‘#N’
'#N=' 给出对象的名称,而 '#N#' 表示该对象,因此在读回对象时,它们将是同一个对象而不是副本:
(let ((a (list 1))) (setcdr a a)) => (1 . #0) ‘#N=’ ‘#N#’
'N' 表示为十六进制数 ('#x2a')
‘#xN’
'N' 表示为八进制数 ('#o52')
‘#bN’
'N' 表示为二进制数 ('#b101010')
‘#bN’
字符串文本属性
‘#(…)’
字符表
‘#^’
哈希表
‘#s(hash-table …)’
字符
‘?C’
字节编译文件中的当前文件名
‘#$’ 请参阅文档字符串和编译
跳过接下来的 N 个字符
‘#@N’ 这在字节编译文件中使用,并不意味着在 Emacs Lisp 源文件中使用
注释
注释是写在程序中的文本,仅供阅读程序的人使用,对程序的含义没有影响:
- 在 Lisp 中,如果未转义的分号 ; 不在字符串或字符常量内,则它会开始注释
- 注释继续到行尾
- Lisp 阅读器会丢弃注释; 它们不会成为 Lisp 对象的一部分,这些对象代表 Lisp 系统中的程序
'#@count' 结构 会跳过下一个 count 字符,
这对于程序生成的包含二进制数据的注释很有用 Emacs Lisp 字节编译器在其输出文件中使用它(参见字节编译)。但是,它不适用于源文件
编程类型
Emacs Lisp 中的类型一般分为两类:
- 与 Lisp 编程有关的类型
- 与编辑有关的类型
前者以一种或另一种形式存在于许多 Lisp 实现中。后者是 Emacs Lisp 独有的
整数类型
在底层,有两种整数:称为 fixnums 的 小整数 和称为 bignums 的 大整数 :
fixnum 的值范围取决于机器
最小范围是 -536,870,912 到 536,870,91 (30 位;即 -2**29 到 2**29 - 1),但许多机器提供的范围更广
- Bignums 可以具有任意精度
溢出 fixnum 的操作将改为返回 bignum
- 所有数字都可以用 eql 或 = 进行比较;
- fixnums 也可以与 eq 进行比较
- 可以在任何对象上使用谓词 fixnump 和 bignump
- 要测试一个整数是 fixnum 还是 bignu ,可以将其与 most-negative-fixnum 和 most-positive-fixnum 进行比较
读取语法
整数的读取语法是一个(以十为基数)数字序列:
- 开头有一个可选的正负符号
- 结尾有一个可选的句点
Lisp 解释器生成的打印表示从不具有前导 + 或结尾 .
-1 ; The integer -1. 1 ; The integer 1. 1. ; Also the integer 1. +1 ; Also the integer 1.
浮点型
浮点数是 科学记数法 的计算机等价物; 可以将浮点数视为分数加上 10 的幂:
- 有效数字的精确数量和可能的指数范围是特定于机器的
- Emacs 使用 C数据类型double 来存储值,在内部它记录的是 2 的幂,而不是 10 的幂
浮点数的打印表示需要 一个小数点 (后面 至少有一个数字 ),一个 指数 ,或 两者兼而有之
例如 '1500.0', '+15e2', '15.0e+2', '+1500000e-3', '.15e4' 是浮点数1500的五种写法,它们都是等价的
字符类型
Emacs Lisp 中的字符只不过是一个 整数 。换句话说,字符由它们的 字符代码 表示
例如,字符 A 表示为整数 65
程序中偶尔会使用单个字符,但更常见的是使用 字符串 ,它是由 字符组成的序列 :
字符串和缓冲区中的字符目前限制在 0 到 4194303 的范围内
22 位整数,参见字符代码
- 代码 0 到 127 是 ASCII 代码; 其余的是非 ASCII
- 代表键盘输入的字符范围更广,可以对修饰键(如 Control 、Meta 和 Shift)进行编码
为了消息的缘故,有一些特殊的函数可以生成人类可读的字符文本描述。请参阅描述帮助消息的字符
基本字符语法
由于字符实际上是整数,因此字符的打印表示是十进制数。这也是字符的一种可能的读取语法
但在 Lisp 程序中以这种方式编写字符并不是清晰的编程 应该始终使用 Emacs Lisp 为字符提供的特殊读取语法格式。这些语法格式以问号开头
字母数字字符的通常 读取语法 是 问号后跟字符 ; 因此, ?A 表示字符 A, ?B 表示字符 B, ?a 表示字符 a
?Q ⇒ 81 ?q ⇒ 113
可以对标点字符使用相同的语法。但是,如果标点符号在 Lisp 中具有特殊的句法含义,则必须用 '\' 将其引用
例如,'?\(' 是左括号字符的书写方式 同样,如果字符是 '\ ,则必须使用第二个 '\' 来引用它:'?\\'
可以将字符 control 、退格、制表符、换行符、垂直制表符、换页、空格、回车、del 和转义表示为 ?\a , ?\b , ?\t , ?\n , ?\v, ?\f . ?\s , ?\r , ?\d 和 ?\e
?\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
注意:'?\s' 后跟一个破折号有不同的含义,它将 Super 修饰符应用于后面的字符
这些以反斜杠开头的序列也称为 转义序列 ,因为反斜杠扮演转义字符的角色:
这与字符 ESC 无关
'\s' 用于字符常量; 在字符串常量中,只写空格
- 在没有特殊转义含义的任何字符之前允许使用反斜杠,并且无害; 因此,'?\+' 等价于 '?+'
- 没有理由在大多数字符之前添加反斜杠。但是,必须在任何字符 '()[]\;"_ ' 之前添加反斜杠,并且应该在任何字符 '|'`#.,' 之前添加反斜杠,以避免混淆用于编辑 Lisp 的 Emacs 命令代码
- 还应该在类似于前面提到的 ASCII 字符的 Unicode 字符之前添加反斜杠,以避免混淆阅读您的代码的人
Emacs 将突出显示一些非转义的常见混淆字符,例如 ''' 以鼓励这一点 还可以添加在空格字符(例如空格、制表符、换行符和换页符)之前的反斜杠 但是,使用易于阅读的转义序列之一(例如 '\t' 或 '\s')而不是实际的空格字符(例如一个制表符或一个空格) 如果你写反斜杠后跟一个空格,你应该在字符常量后面写一个额外的空格来将它与下面的文本分开
通用转义语法
除了特殊重要控制字符的特定转义序列之外,Emacs 还提供了几种类型的转义语法,可以使用它们来指定 非 ASCII 文本字符 :
通过其 Unicode 名称 指定字符(如果有): ?\N{NAME} 表示名为 NAME 的 Unicode 字符
'?\N{LATIN SMALL LETTER A WITH GRAVE}' 等价于 ?à 并表示 Unicode 字符 U+00E0 为了简化输入多行字符串,还可以将名称中的空格替换为非空的空白序列(例如,换行符)
通过其 Unicode 值 指定字符: ?\N{U+X} 表示具有 Unicode 代码点 X 的字符,其中 X 是十六进制数。此外, ?\uxxxx 和 ?\Uxxxxxxxx 分别表示代码点 xxxx 和 xxxxxxx ,其中每个 x 是单个十六进制数字
例如,?\N{U+E0}、?\u00e0 和 ?\U000000E0 都等价于 à 和 '?\N{LATIN SMALL LETTER A WITH GRAVE} Unicode 标准仅定义代码点至 'U+10ffff ,因此如果指定的代码点高于此,Emacs 会发出错误信号
通过 十六进制字符代码 指定字符:十六进制转义序列由 反斜杠 、 x 和 十六进制字符代码 组成
因此,'?\x41' 是字符A,'?\x1' 是字符 C-a ,而 ?\xe0 是字符 à(带有重音的 a) 可以使用任意数量的十六进制数字,因此可以用这种方式表示任何字符代码
通过 八进制字符代码 指定字符:一个八进制转义序列由 一个反斜杠 后跟 最多三个 八进制数字 组成
因此,字符 A 为 ?\101 ,字符 C-a 为 ?\001 ,字符 C-b 为 ?\002 但只能以这种方式指定八进制代码 777 以内的字符
这些转义序列也可以用在字符串中
控制字符语法
可以使用另一种读取语法来表示 控制字符 。这由 一个问号 后跟一个 反斜杠 、 插入符号 和 相应的非控制字符 组成,无论是大写还是小写
例如,'?\^I' 和 '?\^i' 都是字符 C-i 的有效读取语法,该字符的值为 9 也可以使用 'C-' 代替 '^' , 因此,'?\C-i' 等价于 '?\^I' 和 '?\^i'
?\^I ⇒ 9 ?\C-I ⇒ 9
在字符串和缓冲区中,唯一允许的控制字符是那些存在于 ASCII 中的字符; 但出于键盘输入目的,可以使用 C- 将任何字符转换为控制字符
这些非 ASCII 控制字符的字符代码包括 2**26 位以及对应的非控制字符的代码 并非所有文本终端都可以生成非 ASCII 控制字符,但使用 X 和其他窗口系统可以直接生成它们
由于历史原因,Emacs 将 DEL 字符视为 ? 的控制等价物:
?\^? ⇒ 127 ?\C-? ⇒ 127
因此,目前无法使用'\C-'来表示字符 Control- ,它是 X 下有意义的输入字符。改变这一点并不容易,因为各种 Lisp 文件都以这种方式引用 DEL 为了表示要在文件或字符串中找到的控制字符,推荐使用 '^' 语法 对于键盘输入中的控制字符,我们更喜欢 C- 语法 你用哪一个不影响程序的意思,但可能会指导阅读它的人的理解
元字符语法
元字符 是使用 META 修饰键 键入的字符。表示此类字符的整数设置了 2**27 位。为此修饰符和其他修饰符使用高位,以使广泛的基本字符代码成为可能
在字符串中,附加在 ASCII 字符上的 2**7 位表示元字符 因此,可以放入字符串中的元字符的编码范围从 _128_ 到 _255_ ,并且是普通 ASCII 字符的元版本 有关字符串中 META 处理的详细信息,请参阅将键盘事件放入字符串中
元字符的读取语法使用 \M- :
例如, '?\M-A' 代表 M-A
可以将 \M- 与八进制字符代码、 \C- 或任何其他字符语法一起使用
将 M-A 写为 '?\M-A' 或 '?\M-\101' 同样,可以将 C-M-b 写为 '?\M-\C-b 、'?\C-\M-b' 或 '?\M-\002'
其他字符修饰符位
图形字符的大小写 由其 字符代码 表示
例如, ASCII 区分字符 a 和 A 。但是 ASCII 无法表示控制字符是大写还是小写
Emacs 使用 2**25 位来指示在键入控制字符时使用了 shift 键
这种区别只有在图形显示上才有可能,例如 X 上的 GUI 显示,而文本终端不报告区别
Shift位的 Lisp 语法是 \ S- ; 因此 ?\C-\ S-o 或 ?\C-\ S-O 表示 shift-control-o 字符
X Window 系统定义了另外三个可以在字符中设置的修饰符位:hyper , super 和 alt 。这些位的语法是 \H- , \s- 和 \A-
在这些前缀中大小写很重要。因此,'?\H-\M-\A-x' 代表 Alt-Hyper-Meta-x 从数值上看,位值是 2**22 用于 alt,2**23 用于 super,2**24 用于 hyper
符号类型
GNU Emacs Lisp 中的 符号 是一个 有名字的对象 。 符号名称 用作符号的 打印表示 。在普通的 Lisp 使用中,通过使用一个 obarray ,一个符号的名称是 唯一 的
没有两个符号具有相同的名称
符号可以用作 变量 、 函数名 或 保存属性列表 。或者它可能仅用于与所有其他 Lisp 对象不同,以便可以可靠地识别它在数据结构中的存在
在给定的上下文中,通常只打算使用这些用途中的一种,但是可以独立地以所有这些方式使用一个符号
名称以冒号 ( : ) 开头的符号称为 关键字符号 。这些符号自动充当常量,通常仅通过将未知符号与一些特定替代符号进行比较来使用
符号名称可以包含任何字符。大多数符号名称由 字母 、 数字 和 标点符号 以及 -+=*/ 组成
这样的名称不需要特殊的标点; 只要名称看起来不像数字,名称的字符就足够了
如果是,请在名称的开头写一个 \ 以强制解释为符号
- 字符 _!@$%^&:<>{}?~ 很少使用,但也不需要特殊的标点符号
- 任何其他字符都可以包含在符号名称中,方法是使用 反斜杠 对其进行转义
然而,与它在字符串中的使用相反,符号名称中的反斜杠只是简单地引用反斜杠后面的单个字符
例如,在字符串中, '\t' 代表制表符; 然而,在符号名称中, '\t' 仅仅引用了字母 't' 要使名称中包含制表符的符号,您必须实际使用制表符(前面带有反斜杠),但很少有做这样的事情
Common Lisp 注意:在 Common Lisp 中,小写字母总是折叠成大写字母,除非它们被明确转义 在 Emacs Lisp 中,大写和小写字母是不同的
以下是符号名称的几个示例:
foo ; A symbol named ‘foo’. FOO ; A symbol named ‘FOO’, different from ‘foo’. 1+ ; A symbol named ‘1+’ ; (not ‘+1’, which is an integer). \+1 ; A symbol named ‘+1’ ; (not a very readable name). \(*\ 1\ 2\) ; A symbol named ‘(* 1 2)’ (a worse name). +-*/_~!@$%^&=:<>{} ; A symbol named ‘+-*/_~!@$%^&=:<>{}’. ; These characters need not be escaped.
请注意,第四个示例中的 + 被转义以防止它被读取为数字 在第六个示例中这不是必需的,因为名称的其余部分使其作为数字无效
符号名称作为其打印表示的规则的一些例外:
- ## 是名称为 空字符串的内部符号 的打印表示
- #:foo 是名称为 foo 的非内部符号 的打印表示