UP | HOME

2 数据类型

Table of Contents

Lisp 对象是 Lisp 程序使用和操作的一段数据。对于我们的目的,类型或 数据类型 是一组可能的对象:

Emacs 中内置了一些 基本的对象类型 。这些,所有其他类型的构造,被称为 原始类型 。每个对象都属于一种且仅一种原始类型:

Lisp 与许多其他语言的不同之处在于它的对象是 自类型化 的:每个对象的原始类型都 隐含 在对象本身中

例如,如果一个对象是一个向量,那么没有任何东西可以将它视为一个数字

Lisp 知道它是一个向量,而不是一个数字
在大多数语言中,程序员必须声明每个变量的数据类型,并且类型是编译器知道的,但不会在数据中表示出来

Emacs Lisp 中不存在这样的类型声明。Lisp 变量可以具有任何类型的值,并且它会记住您存储在其中的任何值,类型和所有内容

实际上,少数 Emacs Lisp 变量只能接受某种类型的值。请参阅带限制值的变量

本章描述了 GNU Emacs Lisp 中每种标准类型的用途、打印表示和阅读语法。关于如何使用这些类型的详细信息可以在后面的章节中找到

打印表示和读取语法

  • 对象的 打印表示 是 Lisp 函数 print 为该对象生成的 输出格式 ,每种数据类型都有 唯一的 打印表示
  • 对象的 读取语法 是 Lisp 函数 read 为该对象接受的 输入格式 。这不一定是唯一的; 许多种类的对象有 不止一种 语法

在大多数情况下,对象的打印表示也是对象的读取语法。然而,有些类型 没有读语法 ,因为在 Lisp 程序中将这些类型的对象 作为常量输入是没有意义的 。这些对象以 散列符号 打印,由字符 #<描述性字符串 (通常是类型名称后跟对象名称)和结束符号 > 组成。例如:

(current-buffer)
⇒ #<buffer objects.texi>

哈希表示法根本无法读取,因此 Lisp 阅读器在遇到 #< 时会发出错误无效读取语法的信号

在其他语言中,表达式是文本; 它没有其他形式 

在 Lisp 中, 表达式 主要是 Lisp 对象 ,其次是作为 对象读取语法的文本

很多时候不需要强调这个区别,但是一定要放在脑后,不然偶尔会很迷茫

当以交互方式执行表达式时,Lisp 解释器:

  1. 首先读取它的文本表示,生成一个 Lisp 对象
  2. 然后 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 进行比较
  • 可以在任何对象上使用谓词 fixnumpbignump
    • 要测试一个整数是 fixnum 还是 bignu ,可以将其与 most-negative-fixnummost-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 的非内部符号 的打印表示

序列类型