# LAND OF LISP

By Ivory

![](/files/-MhZxQpD49cMKfSiOAWy)

![](/files/-MhZxVgcvKa11EHRyk9t)

![](/files/-MhZx_5s_l4TRBodKGYE)

![](/files/-MhZxcM31-29Vv-4OhXd)

![](/files/-MhZxfkSnkxVrpOiKy3-)

![](/files/-MhZxj1k30_6G2hhXRRm)

![](/files/-MhZxlbSuhxneKxbDn7C)

![](/files/-MhZxoPsGiq5xMavTuc1)

![](/files/-MhZxs8CZveqbvkWk5kn)

![](/files/-MhZxwCTCTa8064ta1SW)

![](/files/-Mhb_31OkIa70itZYJfS)

![](/files/-Mhb_BCEzv_zHsaxlG3T)

![](/files/-Mhb_BCFXHrugVI-Dc0x)

![](/files/-Mhb_BCGc_COIGXBeGPZ)

![](/files/-Mhb_BCHXJ2qjn7CiGbO)

![](/files/-Mhb_BCIRChnhgidxsYz)

![](/files/-Mhb_BCJpFtnfhgvGwfs)

![](/files/-Mhb_BCKKFgVMTolwTun)

![](/files/-Mhb_BCLqHj2eQZbryQX)

![](/files/-Mhb_BCMQNXbBRwrDBu-)

![](/files/-Mhb_BCN6m3T8zsveIhP)

![](/files/-Mhb_BCPmTwYVGYPmO6v)


# 引言

By Ivory

学习 elisp 没有找到中文文档，~~**翻译自用**~~

**以下并非翻译，而是作者阅读原文档后，具有评论性质的翻译。**

**原因有二：其一，学Lisp的人都有能力直接看原文档，其实我是翻译了个寂寞；其二，翻译也只是学习lisp顺带的副作用，一个人翻着没人看的文档，加些评论，注释，让着漫漫长夜没那么寂寞。**

因为就一个人，这个文档的完成可能是一个漫长的过程，如果有志同道合的朋友，欢迎联系我．（最好有lisp基础）

翻译中，如果有某位朋友想先阅读某几个未翻译的章节，可以联系我，我会优先翻译。

有任何的翻译错误，或是更好的翻译建议，欢迎向我**发邮件告知**。（因为还在翻译，所以章节排版会比较混乱，这个就先别发邮件，后续会慢慢调整）

（另外推荐学习 lisp, 强烈推荐田春翻译的 《**实用Common Lisp编程》**，以及他翻译&#x7684;**《ANSI Common Lisp》以及《On Lisp》**）

**本文档优先翻译概念部分，后续会把函数细节相关补上。**

**联系邮箱 <zheng@stu.xmu.edu.cn>**

2021-8-14　　３:２２


# 进度 & 计划

当前计划每日翻译2、3个节点，具体看内容多少

周末会统一润色，可能会翻译。

~~从效率角度出发，骄傲的使用Google翻译 + 人工校对调整的方式，快捷准确且高效~~ 完全采用人工翻译

从学习角度出发，完全使用人工翻译 + 人工校对的方式翻译

第九章(第一版) 完成于 很久之间

第二章(第一版) 完成于 2021 年 10 月 9 日 凌晨 2:06

~~预计 2021 年 10 月 16 日 前，完成第三章数字的翻译和校对~~

第三章(第一版) 完成于 2021 年 10 月 10 日 晚上 19:29

第十章(第一版) 完成于 2021 年 10 月 11 日 晚上 23:28

第十九章(第一版) 完成于 2021 年 10 月 17 日 下午 14:54

~~预计 2021 年 10 月 16 日 前，完成第四章字符串的翻译和校对~~

预计 2021 年 10 月 24 日 前，完成第四章字符串的翻译和校对 （未完成）

前段时间忙于秋招，没有更新翻译，现在秋招结束，继续翻译任务。

虽然就投了一家，但是是心仪的企业，也算是有个不错的结尾。

昨天学习 twistted 框架，看了一位老哥的入门翻译，感觉翻译真的不容易，既要能传达原文的意思，又需要通顺自然。然后想到自己的翻译，感觉自己都不愿意回头看，因此：

下面一周停止新章节翻译，将以翻译文档的翻译统一风格和术语，并润色一下。

~~计划: 2021 年 11 月 19 日 - 2021 年 11 月 25 日 完成以翻译章节的润色，术语统一，格式统一。~~

2021 年 11 月 - 2022 年 6 月期间因为学业停止翻译，现在空闲，继续翻译。

计划: 2022 年 6 月 17 日 - 2022 年 6 月 24 日 完成翻译的润色，术语统一，格式统一。

第十章(第二版) 完成于 2022 年 11 月 30 日 晚上 23:00


# 一些记录

interned 被译为 ~~被注册的~~ 被收录的。

slot 译为 槽\~\~，后续可能会改成 单元\~\~


# 2 Lisp 数据类型

Lisp 对象 是指用于构建 Lisp 程序的一小段数据。在本小结中，类型(type)或数据类型(data type)是指一系列对象的封装集合。

每个对象都至少属于一种数据类型。相同类型的对象通常拥有相似的结构，拥有相似的功能。类型可以被覆盖，也就是说一个对象可以同时属于两种及以上的类型。因此，我们通常会问某个对象**属于**哪种类型，而不会说这个对象的类型**是**什么。（注意表述）

Emacs 中内建一些基础的数据类型。这些用于构造其他更复杂结构的类型称为原始类型(primitive types)。每个对象必定属于某一种原始类型。原始类型包含：整型，浮点型，点对，符号，字符串，向量，哈希表，子例程，字节码函数，记录，以及一些emacs中与编辑相关的特殊类型，比如缓冲区。（详见编辑类型）

每个原始类型都有一个对应的Lisp函数，用于检查某个对象是否属于该类型。

与大多数语言不同的是，Lisp是self-typing的：每个对象的原始类型隐含在该对象本身。例如，若一个对象是向量，那就不能将它当成数字进行处理；Lisp知晓该对象的原始类型是向量，而非数字。

在大多数语言中，程序员必须为每个变量进行类型声明，以便编译器处理，但这些类型并没有在数据内部体现出来。在Emacs Lisp中，不存在类似的类型声明。Lisp变量可以存储任何类型的值，同时它不仅记录了值，而且记录了类型以及其他所有信息。(实际上，Emacs Lisp中较小的数字通常只能属于某个特定的原始类型)

本章节介绍了GNU Emacs Lisp中每种标准化类型(standard types)的设计目的(purpose)、打印表示(printed representation)以及读取语法(read syntax)。细节会在后续的章节中介绍。

译者注：

上面用到了对象和变量两种表述，这是两个不同的概念，读者需要注意区分。Lisp中概念很多，切不可混淆。

上面的Lisp对象是一种表述，指某个“物体”。不要和面向对象类语言中的对象混淆，原始的Lisp中并没有继承、多态等特性，也没有原型继承，类继承这些概念。

Lisp中的变量与绝大多数语言中的变量都不太一样，Lisp中的变量通常都需要自求值，将代码表述(也就是上面的读取语法)转换为实际的Lisp对象，这一过程称为解释器的读取；而将Lisp对象的代码表述称为打印表示。这有点类似很多语言中的数据序列化，但是不完全相同：序列化通常是完全可逆的，但这里打印表示和Lisp对象的转换有时是单向的。


# 2.1 打印表示 和 读取语法

Lisp 对象的文本表示

对象的**打印表示**（printed representation）是 **Lisp 打印器**（Lisp printer）（也就是函数prin1）所生成的，该类型对象的文本表示。每种数据类型都有它们各自的打印表示。**读取语法**（read syntax）是指可以被Lisp 读取器理解的，某种类型对象的文本表示。读取语法并不一定是特定的。很多对象拥有一种以上的读取语法。详情查阅 读取和打印。

绝大多数情况下，一个对象的打印表示同时也是这个对象的读取语法。不过，也有一些类型的对象没有读取语法。对于这些对象而言，使用读取语法将其输入 Lisp 没有多少现实意义。这些对象统一使用 哈希记法（hash notation），也就是由 字符 '#<'，一段描述性的字符串（通常来说是类型名加上对象名）, 以及符号 '>'，组成。比如：

```
(current-buffer)
    => #<buffer objects.texi>
```

哈希记法完全不可读（指无法被Lisp读取器理解），因此当你尝试使用哈希记法向 Lisp 程序输入数据时，会抛出 invalid-read-syntax 错误。（不论你有没有使用 '#<' ）。

在其他语言中，表达式就是文本，而没有其他形式。在 Lisp 中，表达式是一个 Lisp 对象，同时也可以是这个对象的读取语法。一般来说，两者没有区分的必要，但是你最好知道其中的区别，不然在后续的学习中你会经常感到困惑。

当你以交互的方式对一个表达式求值时，Lisp 解释器首先会读取这个表达式的文字性表示，然后生成一个 Lisp 对象，接着再对这个 Lisp 对象进行求值（详情查阅 求值）。需要注意的是，读取和求值是两个不同的过程。读取会返回文本对应的 Lisp 对象；但之后，这个 Lisp 对象可能被求值，也可能不被求值；查阅 输入函数，你可以找到最基础的用于读取对象的函数：read。


# 2.2 特定的读取语法

Emcas Lisp 中有很多特殊的哈希记法，来表示一些特殊的对象。

| 记法                       | 解释                                                                                             |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| '#<...>'                 | 没有读取语法的对象的打印表示                                                                                 |
| '##'                     | 名称为空字符串的内部注册（interned）符号的记法                                                                    |
| '#''                     | 这个是 function 的缩写，（查阅 匿名函数）                                                                     |
| '#:'                     | 未注册(uninterned)符号的记法。比如名称为foo 的符号： '#:foo'。（查阅 符号类型）                                           |
| '#N'                     | <p>循环结构的记法，其中的 N 是回环的位置。</p><p>(let ((a (list 1)))</p><p>  (setcdr a a))</p><p>=> (1 . #0)</p> |
| <p>'#N='</p><p>'#N#'</p> | '#N=' 给一个对象命名，而'#N#'代表那个对象，所以，当读取这个对象时，它们读取的是同一个对象，而不是副本                                       |
| '#xN'                    | 表示十六进制数字  ( '#x2a')                                                                            |
| '#oN'                    | 表示八进制数字 ('#o52')                                                                               |
| '#bN'                    | 表示二进制数字 ('#b101010')                                                                           |
| '#(...)'                 | 字符的文本属性 （查阅 文本属性和字符串）                                                                          |
| '#^'                     | 字符表 （查阅字符表类型)                                                                                  |
| '#s(hash-table ...)      | 哈希表 （查阅哈希表类型）                                                                                  |
| '?C'                     | 字符 （查询 基础字符语法）                                                                                 |
| '#$'                     | 当前字节码文件的文件名 (查阅 文档和编译）。这并不意味着在 Emacs Lisp 源文件中使用。                                              |
| '#@N'                    | 跳过余下的 'N' 个字符。这个一般在字符编译文件中使用，这并不意味着在 Emcas Lisp 源文件中使用。                                        |


# 2.3 注释

注释是程序中用来向人类自我注解的文本，而这部分文本不会对程序造成实质性的影响。在 Lisp 中，用一个未转义的分号（ ' ; ' ）作为注释的开始，不过这个分号不能包含在字符常量和字符串常量中。注释的范围持续到行末。Lisp 读取器会略过这部分，因此不会被读取进 Lisp 系统内部。

&#x20;'#@count' 会跳过前count个字符，这在二进制数据中注释很有用。Emacs Lisp 编译器会在其输出文件中使用。不过这并不是说在源代码中使用。

查阅 注释小助手，学习如何更好的组织注释。


# 2.4 编程类型

Emacs Lisp 中有两大类型: 一大类型用于 Lisp 编程, 另一大类用于 文本编辑. 前者再大多数的 类Lisp 语言中都有, 而后一类是 Emcas Lisp 独有的.

译者注：

这里的两大类型是从功能上分的类型，而不是语言内部的类型，直接理解为两大类即可：一类用于Lisp语言编写通用程序，另一类用于方便Emacs Lisp实现Emacs功能。


# 2.4.1 整型

不含小数部分的数字

隐藏在整型黑盒内部的, 是两种整型: 一种(fixnums)用于较小的数字 , 另一种(bignums)用于较大的数字.

其中 fixnums 类型的取值范围取决于具体的机器. 最小的范围是-536,870,912 到 536,870,911 (30位), 不过大多数机器支持的范围更大.

bignums 拥有任意大小的精度. 数字在 fixnums 类型中溢出时便会自动返回 bignums 类型的版本.

所有的数字都可以使用 eql 或 = 进行比较. fixnums 也可以使用 eq 比较. 你可以将数字和 most-negative-fixnum 和 most-positive-fixnum 比较, 以此来判断一个数是 fixnum 还是 bignum. 当然, 你还可以使用谓语 fixnump 和 bignump 来测试。

整型数的阅读语法是十进制的数字序列表示, 可以加上额外的正负号, 或者点, 打印表示并不会包含正负号, 也不会出现点。

```
-1               ; 整数 -1.
1                ; 整数 1.
1.               ; 还是整数 1.
+1               ; 同样是整数 1.
```

详情查阅 数字章节


# 2.4.2 浮点型

包含小数部分的数字

浮点型数字采用科学计数法。你可以把浮点数当成有小数的十进制数字。浮点数有效数字的位数和范围取决于你的机器。Emacs 使用 C 内部的 double 数据类型储存数据，此外内部记录使用二进制, 而非十进制。

浮点型数字的打印表示包含小数点, 或指数形式, 或两者都包含。比如, '1500.0', '+15e2', '+1500000e-3', 以及 '.15e4' 是 **浮点数** 1500 的五种打印表示，它们都是等价的。


# 2.4.3 字符型

Emacs Lisp 中的字符本质上是整型数。换句话说，字符是整型数的另一种表示形式。比如，字符 A 实际上表示整数 65。

编程中偶尔会使用到单个字符，不过使用字符串会更加常见一些。字符串就是若干字符的组合体。

字符串和缓冲区内的字符通常被限制在 0 到 4194303内, 也就是 22 位. 其中 0 到 127 称为 ASCII 码, 余下的称为 非-ASCII 码。字符在表示按键映射时十分有用，除了常规按键，它们也会用来表示一些控制键, 比如 Control， Meta， 以及 Shift 等。

有一些特殊的函数可以为消息生成人类可读的字符文本描述。详情查阅 字符描述.


# 2.4.3.1 基本字符语法

字符本质上是整数，其打印表示是十进制数字。通常十进制数字也可以作为字符的打印输入，不过这么写容易和整数混淆。因此你应该使用Emacs Lisp提供的特定字符读取语法。这些读取语法以一个问号开头。

某个字母字符的读取语法通常是问号加上该字符；因此，‘?A‘为字符'A'的表示语法，'?B'为字符'B'的表示语法，'?a'为字符'a'的表示语法。

例如

```
?Q => 81
?q => 113
```

标点符号字符通常有类似的读取语法。不过若该标点在Lisp中有特殊语法意义，那么你需要用反斜杠'\\'将其转义。例如，'?\\('为开括号字符的读取语法。类似的，如果你想让Lisp读取字符'\\'，那么你需要这么写'?\\\\'。

你可以使用'?\a'，'?\b'，'?\t'，'?\n'，'?\v'，'?\f'，'?\s'，'?\r'，'?\d'，'?\e'分别表示control-g、删除符、制表符、换行符、垂直制表符、换页符、空格、回车、删除、ESC。例如：

```
?\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
```

这类由反斜杠开头的序列也称为转义序列，其中开头的反斜杠起到转译的作用；这里的转义和ESC字符没啥联系（英文中两种都写作Escape）。'\s'通常在字符常量中使用，在字符串常量中，直接敲空格就行。

反斜杠对于不需要转译的字符是无害的，被允许使用的。因此'?\\+'和'?+'是等价的。绝大多数的字符前都不要使用反斜杠。但是你必须在字符'()\[]\\;"'以及'|'\`#.,'前加上反斜杠，以防歧义。你必须在前文提到的 Unicode 字符前加上反斜杠，以防止误导阅读你代码的人。Emacs本身会将未转译的，容易引起混淆的字符高亮，以提醒你添加反斜杠。你同样可以在诸如空格、制表符、换行符、换页符前添加反斜杠转译，但通常使用上述的语法会让代码更容易阅读。


# 2.4.3.2 通用转义语法

除了特定的转义序列，Emacs提供了几种转义语法，用来表示非ASCII字符。

1. 使用Unicode名称。?\N{NAME} 表示名称为NAME的Unicode字符。例如，'?\N{LATIN SMALL LETTER A WITH GRAVE}'和`?à` 等价，它们都表示 U+00E0 这个Unicode字符。为了简化多行字符串输入，你可以使用非空序列的名称来替代一些空白符（比如换行符）。
2. 使用Unicode值。?\N{U+X} 表示指向X位置的Unicode字符，其中X为十六进制数字。同样的，?\uxxxx和 ?\uxxxxxxxx 分别表示指向位置xxxx和位置xxxxxxxx的Unicode,其中x为单十六进制数。比如，?\N{U+E0}，?\u00e0和?\u000000e0和'?\N{LATIN SMALL LETTER A WITH GRAVE}'和`?à` 等价。Unicode标准中定义最大值为'U+10ffff'，因此如果你使用了更大的位置，Emacs将会抛出一个错误。
3. 使用十六进制码。十六进制转义序列包含一个反斜杠，'x'，以及十六进制字符码。比如，'?\x41'表示字符A，'?\x1'表示字符C-a，?\xe0表示字符`à` （重音a）。你可以使用该方法表示任何字符。
4. 使用八进制码。八进制转义序列包含一个反斜杠加上三个八进制数字。比如，'?\101'表示字符A，'?\001'表示字符C-a，'?\002'表示字符C-b。这个方法最多只能表示到八进制代码777位置的字符。

这些转义序列同样可以在字符串中使用。


# Page 2


# Page 3


# Page 4


# Page 1


# 2.4.4 符号类型

在 GNU Emcas Lisp 中，符号 (symbol) 是一个具名 (name) 对象。符号的名字就是符号的打印形式。在常规的 Lisp 实现中, 会有一个全局唯一的 obarray 来记录这些符号。在同一个 obarray 内，所有的符号都应当有自己唯一的名字。

符号可以作为变量，函数名使用，也可以用来指定一个属性列表。（）在给定的语境下, 符号通常只有其中一个用途。不过, 你完全可以同时使用它的三种用途, 它们是相互独立的.

以 ':' 开头的符号称为关键字符号, 这些符号会自动的作为常量来使用。通常只在与位置符号的比较中使用。

符号的名字可以包含任何字符。大多数的符号名字由字母，数字，以及一些标点 '-+=\*/'构成。这个样的名字不需要特殊的符号转义，只要它看起来不像数字。(如果很像数字, 那你需要在开头加上'\\', 以强制解释器把它当成符号)。 字符 '\_\~!@$%^&:<>{}?' 很少使用，不过它们也不需要转义。其他的符号则需要反斜杠转义。不过与字符串中转义的方法不同，符号名称中的反斜杠只对紧挨其后的单个字符进行引号引用。举个例子，在字符串里 '\t' 表示一个制表符; 但在符号名称中, '\t' 只是表示被引号引起来的 't'. 如果你想在符号名中中键入制表符，那你需要真的按下制表符(前面加上 '\\' 转义)。不过通常很少有人这么做。

Common Lisp note: 在 Common Lisp中, 所有的小写字母都会转换成大写字母, 除非它们被显示的转义. 但在 Emcas Lisp 中, 大写字母和小写字母本来就是独立的.

这里有几个例子。注意：第四个例子中的 '+' 被转义，不然这个表达式会被当成整数读取。在第六个例子中使用了很多标点，不过它们并不需要转义。

```
foo                 ; 一个名称为 ‘foo’ 的符号.
FOO                 ; 一个名称为 ‘FOO’ 的符号, 和上面那个 ‘foo’ 不同.
1+                  ; 一个名称为 ‘1+’ 的符号
                    ;   (不是 ‘+1’, '+1' 是个整数).
\+1                 ; 一个名称为 ‘+1’ 的符号
                    ;   (这个符号可读性不是很好).
\(*\ 1\ 2\)         ; 一个名称为 ‘(* 1 2)’ 的符号 (很糟糕的符号名).
+-*/_~!@$%^&=:<>{}  ; 一个名称为 ‘+-*/_~!@$%^&=:<>{}’ 的符号.
                    ;   这些符号不需要转义.
```

前面说过，符号的名字即为符号的打印表示，不过这里有一个例外。'##' 是一个特殊符号的打印表示，该符号被注册 (interned) 在 obarray 中，其名字为空的字符串。更进一步的说，'#:foo' 是一个未被注册 (uninterned) 在 obarray 中，且其名称为 foo 的符号的打印表示。 (正常情况下, Lisp 读取器会注册所有的符号, 详情查阅 创建符号章节)

译者注：这里 interned 被译为注册，uninterned即为未注册。


# 2.4.5 序列类型

序列是 Lisp 中的有序容器。 在 Lisp 中有两种序列：列表和数组。

列表是最常用的序列。列表可以容纳任何类型的元素，并且可以很轻松的增减元素。详情查阅接下来的小节。

和列表不同，数组是定长的序列。数组还可以分为字符串，向量，字符表以及布尔向量四种类型。向量也可以容纳任何类型的元素，但字符串的元素就只能是字符，类似的，布尔向量的元素只能是 t 和 nil 这样的布尔值。字符表比较像向量，不过字符表是使用字符进行索引的。此外，字符串中的字符可以拥有自己的字符属性，这和缓冲区中的文本类似。相比之下，向量不支持文本属性，即便其元素为字符。

列表，字符串，以及其他数组类型都很相似。比如，它们都有长度，都是从 0 开始索引，到-1结束索引的。序列函数，可以作用在这几种类型上。举个例子，函数 length 可以返回任何序列类型对象的长度。

通常来说，读取器不可能两次读取到相同的序列，因为每次读取的时候，都会即时生成一个新序列。如果你读取同一个序列的读取语法两次，那么你会得到两个内容相同，但对象不同的序列。不过有个例外，空列表 () 总是表示同一个对象，也就是nil。


# 2.4.6 点对类型

点对是 Lisp 中最基础，最常用的容器对象。它包含两个槽(slot)，CAR 槽和 CDR 槽。其中每个槽都可以储存任何 Lisp 对象。通常我们提到点对的CAR时，我们是在说这个点对 CAR 槽所储存的对象。 CDR 同理。

我们熟知的 列表 就是有一系列的点对嵌套组成的，其中每个点对的 CDR 槽都储存着下一个 点对，而最后一个点对的 CDR 槽储存着 nil。点对是构成 Lisp 列表的最基础的组件，我们称任何由点对构成的结构都统称为列表结构。

给 C 程序员的提示：Lisp 列表看起来就像由点对单元组成的链表。在 Lisp 中，指针都是隐式使用的，因此我们不会对 储存值的槽 和 指向数值的指针 进行区分。

点对是 Lisp 的**核心数据结构**。所有不是点对的对象统称为原子。

列表的读取语法和打印表示是相同的。一个列表的读取语法（或打印表示）：一个左括号，任意数量的元素，以及一个右括号。这里有几个例子：

```
(A 2 "A")            ; 包含 3 个元素的列表.
()                   ; 不包含任何元素的列表.
nil                  ; 同上 (空列表).
("A ()")             ; 包含 1 个元素的列表: 这个元素是字符串： "A ()".
(A ())               ; 包含 2 个元素的列表: A 以及一个空列表.
(A nil)              ; 同上.
((A B C))            ; 包含 1 个元素的列表
                     ;   (这个元素是一个包含 3 个元素的列表).
```

以上列表中，括号内的对象都是该列表的元素。也就是说，每个元素都占着一个点对槽。点对的 CAR 槽储存元素，而 CDR 槽则指向列表中的下一个点对，而下一个点对储存着下一个元素。最后的点对的 CDR 槽则指向 nil。

CAR 和 CDR 听上去很怪，这是 Lisp 的历史遗留问题，不要多想。


# 2.4.6.1 点对可视化

列表可以可视化成由点对构成的结构，就像多米诺骨牌。（Lisp 读取器读不懂这样的图，但是可以读懂文本记号。）下面的这个图就表示了一个包含 3 个元素的列表 ( rose violet buttercup):

```
    --- ---      --- ---      --- ---
   |   |   |--> |   |   |--> |   |   |--> nil
    --- ---      --- ---      --- ---
     |            |            |
     |            |            |
      --> rose     --> violet   --> buttercup
```

在上图中，每个格子表示一个槽，而这个槽可以指向一个 Lisp 对象。每对格子（就是两个格子）表示一个点对。其中的箭头表示这个槽指向某个 Lisp 对象。被指的对象只能是原子或另一个点对。

在这个例子里，第一个格子，也就是第一个点对的 CAR 位置，指向（或者说储存）rose（一个符号）。而第二个格子，也就是第一个点对的 CDR 位置，指向了另一对格子，也就是第二个点对。第二个点对的 CAR 指向 violet，而 CDR 指向了第三个点对。第三个（也就是最后一个）点对的 CDR 指向 nil。

这里有这个列表的另一种可视化图：

```
 ---------------       ----------------       -------------------
| car   | cdr   |     | car    | cdr   |     | car       | cdr   |
| rose  |   o-------->| violet |   o-------->| buttercup |  nil  |
|       |       |     |        |       |     |           |       |
 ---------------       ----------------       -------------------
```

没有任何元素的列表称为空列表。空列表本质上和 nil 是等价的。换句话说，nil 是一个符号，同时还是一个列表（空列表）。

下面这个例子是列表 (A ())，和 (A nil)等价：

```
    --- ---      --- ---
   |   |   |--> |   |   |--> nil
    --- ---      --- ---
     |            |
     |            |
      --> A        --> nil
```

下面这个例子会更复杂一些，它展示的是列表 ((pine needles) oak maple)：

```
    --- ---      --- ---      --- ---
   |   |   |--> |   |   |--> |   |   |--> nil
    --- ---      --- ---      --- ---
     |            |            |
     |            |            |
     |             --> oak      --> maple
     |
     |     --- ---      --- ---
      --> |   |   |--> |   |   |--> nil
           --- ---      --- ---
            |            |
            |            |
             --> pine     --> needles
```

这里有另一种画法：

```
 --------------       --------------       --------------
| car   | cdr  |     | car   | cdr  |     | car   | cdr  |
|   o   |   o------->| oak   |   o------->| maple |  nil |
|   |   |      |     |       |      |     |       |      |
 -- | ---------       --------------       --------------
    |
    |
    |        --------------       ----------------
    |       | car   | cdr  |     | car     | cdr  |
     ------>| pine  |   o------->| needles |  nil |
            |       |      |     |         |      |
             --------------       ----------------
```


# 2.4.6.2 点对记法

点对记法 是更加通用，正式的点对记法。这种记法显示的声明了 CAR 和 CDR 部分。使用这种语法，(a . b) 表示一个点对，其中 CAR 部分储存 对象 a，CDR 部分储存 对象b。点对记法之所以比列表记法更加通用，是因为 CDR 部分所指的并不一定是列表。不过点对记法通常会更繁琐一些。比如 '(1 2 3)' 会被写成 '(1 . (2 . (3 . nil)))' 。对于 nil 结尾的列表，列表记法通常更加简洁清爽。当你打印一个列表时，当且仅当点对的 CDR 不是列表的时候，Lisp 才会采用点对记法。

这里有一个点对可视化的例子：

```
    --- ---
   |   |   |--> violet
    --- ---
     |
     |
      --> rose
```

你还可以把点对记法和列表记法融合起来使用，这样可以很好的展示一个 结尾 CDR 非nil 的点对链。比如，(rose violet . buttercup) 和 (rose . (violet . buttercup))就是等价的。这个列表看起来像这样：

```
    --- ---      --- ---
   |   |   |--> |   |   |--> buttercup
    --- ---      --- ---
     |            |
     |            |
      --> rose     --> violet
```

不过 (rose . violet . buttercup) 这种记法是不合理的。如果要解释成 buttercup 处在 CDR 位置，那显然是不合理的，因为 CDR 部分已经被 violet 占着了。

列表 (rose violet) 和 这个是等价的 (rose . (violet))，而且它们都像下图这样：

```
    --- ---      --- ---
   |   |   |--> |   |   |--> nil
    --- ---      --- ---
     |            |
     |            |
      --> rose     --> violet
```

类似的，这个包含 3 个元素的列表 (rose violet buttercup) 和 这个是等价的 (rose . (violet . (buttercup)))。它们看起来是这样的：

```
    --- ---      --- ---      --- ---
   |   |   |--> |   |   |--> |   |   |--> nil
    --- ---      --- ---      --- ---
     |            |            |
     |            |            |
      --> rose     --> violet   --> buttercup
```

2021-8-19  1:18


# 2.4.6.3 关联列表

一种具有特殊结构的列表。

关联列表 association list，也就是我们常说的 alist，是一种有特定结构的列表——这个列表的所有元素都是点对。其中每个元素的 CAR 单元称为 键（a key），而 CDR 部分称为 值 （a value）。这是实打实的键值对（不过有时候关联的值 value 是储存在 CDR 单元的 CAR 槽这个位置的。）关联列表通常被当作 栈 （stacks） 使用，因为从列表的前端插入和去除关联点对实在是太方便了。

举个例子，

```
(setq alist-of-colors
    '((rose . red) (lily . white) (buttercup .yellow)))
```

把具有这个三个点对元素的列表赋给变量 alist-of-colors。对于第一个元素，rose 是键，red 是值。

如果你想看更多关于关联列表的详细介绍，和相关函数，那么请查阅关联列表章节。如果你想了解更高效的键值对集合，那么请查阅哈希表这章。


# 2.4.7 数组类型

数组包括字符串和向量

数组是由任意多的槽组成的 Lisp 对象，和列表类似，每个槽都可以指向任何的 Lisp 对象；和列表不同，数组的内存是一大块连续的内存，因此查找速度会更快，基本上是常数时间。相比之下，列表的查找时间取决于被查元素的位置。（在列表末尾的元素的查找时间要远远长于列表开头的元素。）

Emacs 定义了四种数组：字符串，向量，布尔向量，以及字符表。

字符串是由字符组成的数组，而向量是由任意对象组成的数组。一个布尔向量只能装着 nil 或者 t。这些数组的最大长度可以一直到 fixnum 能取到的最大值，而这个最大值一般取决于具体的机器架构限制和可用内存。字符表是使用字符索引的数组，它可以储存任何对象。

数组的第一个元素的索引是 0，第二个元素的索引是 1，以此类推。这种索引叫做从零开始的索引。举个例子，一个 4 元素数组由0，1，2，3这四个索引切片。最大的索引值应当是数组长度减一。**一旦数组被创建了，它的长度就固定了。**

所有的 Emcas Lisp 数组都是一维的。（绝大多数的其他语言提供多维数组，不过这并不重要；在这里，你可以使用复合的一维数组模拟出一样的效果。）每种数组都有它们自己的读取语法。接下来的几章会详细解释。

另外，时刻记住，数组类型是序列类型的子集。数组类型又包括了字符串类型，向量类型，布尔向量类型以及字符表类型。


# 2.4.8 字符串类型

字符串的本质是字符数组。字符串在 Emacs 中非常有用，毕竟 Emcas 就是伪装成操作系统的**文本编辑器**。举个例子，Lisp 符号的名称，给用户的信息，以及表示缓冲区中的文本。Lisp 中的字符串是常量：对字符串求值会返回相同的字符串。

查阅 字符串和字符章节，以获得更多操作字符串的函数。


# 2.4.8.1 字符串的语法

如何声明你想要的那个字符串

字符串的读取语法是一个双引号，加上任意数量的字符，再用一个双引号收尾。 " 比如这个 "（ "like this"）。如果想在字符串内包含双引号本号，那么你需要加上反斜杠进行转义。比如 "\\"" 就是一个只包含一个双引号的字符串。类似的，如果你想再字符串内包含反斜杠，那你就得用另一个反斜杠去对反斜杠转义，比如：" this \\\ is a single embedded backslash" 。

在字符串中会经常出现新行，不过这个新行符并不需要转义，直接换新行即可，Emcas 会自动处理。但是转义新行符，也就是在反斜杠 '\\' 之后直接转新行，这种新行符会被忽略掉。同样，转义空格也会被忽略掉。

```
"It is useful to include newlines
in documentation strings,
but the newline is \
ignored if escaped."
     ⇒ "It is useful to include newlines
in documentation strings,
but the newline is ignored if escaped."
```


# 2.4.8.2 字符串中的非ASCII码

在 Emacs 中有两中 非ASCII码的文本表示方式：多字节和单字节（详情查阅文本表示章节）。粗略的来说，单字节字符串储存原字节，而多字节字符储存人类可识别文本。在单字节字符串中，所有的字符都是字节，其中，编号在 0 到 255 之间。相比之下，多字节字符中，会包含从 0 到 4194303 个值（详情查阅字符类型）。不论哪种情况，超出 127 的都统称为非ASCII。

你可以按照字面写法直接将非ASCII字符直接写在字符串里。如果一个字符串是从一个多字节的源，比如多字节缓冲区或多字节字符，或多字节文件，读取来的，那么 Emacs 会自动将所有非ASCII码按照多字节字符的方式读取进来，并将这个字符串作为多字节字符串。不过如果这个字符串是从一个单字节源读取的非ASCII字符，那么Emacs 就会按照单字节方式读取，并将这个字符串定义为单字节字符串。

在多字节的字符串中，你也可以不直接按着字面写法写，而是使用转义符号加上这个字符对应的字符码。详情查阅通用转义语法。

如果你在一个字符串内使用了 Unicode 风格的转义序列 '\uNNNN' 或者 '\U00NNNNNN'，那么 Emacs 会自动假定这个字符串是多字节的。

你也可以使用十六进制的转义序列（ '\xn' ) 或者 八进制的转义序列 ( '\n' )。不过要牢记，如果一个字符串包含了十六进制的转义序列或者八进制的，而没有一个 非ASCII码的字面写法 或者 Unicode 风格的编码，那么这些字符串会被直接认定为单字节的。也就是说，默认假定所有的非ASCII字符嗾使以八进制原始字节的形式出现的。

在十六进制和八进制的转义序列中，转义字符可能会出现数字，而这些数字是不合法的十六进制或八进制转义序列。如果字符串的下一个字符是一个数字，那么需要在字符末尾补上一个 '\ '（也就是一个反斜杠加上一个空格，来将编码”矫正“。举个例子， '\xe0\ '表示重音的 'a'。而 '\ '就像前面章节提到过的 反斜杠-新行符；它并不提供什么额外的信息，但是"矫正"了编码。


# 2.4.8.3 不可打印字符

你可以在字符串中使用反斜杠转义序列来表示一些字符（但是不要问好）。比如，你可以这样表达一个有制表符和 C-a 这样的不可读字符的字符串: "\t, \C-a"。详情查阅字符类型。

不过，并不是所有的字符都可以使用反斜杠转义序列表达。一个字符串中能包含的控制字符只有 ASCII 控制字符。字符串并不会区分 ASCII 控制字符的大小写。

坦白来说，字符串并不能包含 meta 字符；不过如果一个字符串被用来当作按键序列是，有一个语法糖可以让你表示 ASCII 版本的 meta 字符。如果你在字符串中使用了 '\M-' 这样的语法来表示 meta 字符，~~那么这将设置字符串中的 2 \*\* 7位~~ （没看懂）。如果这个字符串在 define-key 和 lookup-key 中使用，那么这个数字吗会被 Emacs 理解成 meta 键字符。详情查阅字符类型。

字符串中不能包含 hyper，super 以及 alt 这样的控制字符。


# 2.4.8.4 字符串的文本属性

字符串中的字符可以拥有自己的文本属性。这种设计简化了不同缓冲区间拷贝时的文本属性处理。详情查阅文本属性。具有文本属性的字符串会有一种特别的读取和打印语法：

```
#("characters" property-data...)
```

其中 property-data 包含了 0个及以上的元素，这些元素都是三个一组的形式：

```
beg end plist
```

其中 beg元素 和 end 元素是整数，用来指明字符串中的范围；plist 是一个属性列表，用来描述这个范围内的文本属性。举个例子，

```
#("foo bar" 0 3 (face bold) 3 4 nil 4 7 (face italic))
```

表示了这样的一段文本 'foo bar'，其中前三个字符有个 face 属性，其属性值是 bold （粗体）；而后三个字符也有 face 属性，但值为 italic （斜体）。(第四个字符没有任何文本属性，因此属性列表是 nil。不过在这里，标记 没有文本属性的文字 是没有必要的，Emcas 会默认认为所有没有标记范围的字符没有文本属性。）


# 2.4.9 向量类型

一维数组

向量是可以引用任何 Lisp 对象的一维数组。向量的查找时间同样为常数时间。（在列表中，查找时间和列表的位置成正比。）

向量的打印表示为一个左中括号、元素，以及右中括号。这同时也是它的读取语法。和数字，字符串一样，向量也是自求值的常数。

```
[1 "two" (three)]        ; 一个 3 元素 向量
    => [1 "two" (three)]
```

查看向量章节，以获取和向量相关的函数。


# 2.4.10 字符表类型

字符表是一维数组，不过其以字符进行索引。字符串也因此拥有了一个特性，那就是可读性。比如，字符表可以继承自一个父表，拥有默认值，以及少数的槽用来另作他用。

字符表的打印表示和向量很像，只开头会多一个 "#^"。

字符表章节有更多的操作字符表的函数。字符表的用途有：

* Case Table
* 分类表
* 显示表
* 语法表


# 2.4.11 布尔向量类型

布尔向量 是 lisp 一维数组，其数组元素只能是 t 或 nil。

布尔向量的打印表示和字符串很像。不过布尔向量的打印表示的最前面比字符串多了 "#&"，后面在跟着向量长度。再紧接的字符串的内容以位图的形式描述了布尔向量的内容。字符串中的每一个字符都包含了8个比特的信息，这8位的信息描述了布尔向量的接下来的8个元素，其中1代表t，0代表nil。字符的最后一位的信息对应了布尔向量最后一个元素。

```
(make-bool-vector 3 t)
    => #&3"^G"
(make-bool-vector 3 nil)
    => #&3"^@"
```

上面这个例子中的结果是正确的，因为 'C-g' 的二进制代码是 111， 而 'C-@' 表示代码 000.

如果打印表示中的字符不是8位的，那么多余的字符便没有任何意义。比如下面这个例子，其中的两个布尔向量是 equal的，因为只有前三个位信息被使用了。

```
(equal #&3"\377" #&3"\007")
     ⇒ t
```


# 2.4.12 哈希映射类型

哈希映射表是一种非常快的查找表，表面上看和关联链表很相似，都是从键到值的映射，但是更快。哈希映射表的打印表示中包含了属性和内容，就像下面这样：

```
(make-hash-table)
    => #s(hash-table size 65 test eql rehash-size 1.5
                             rehash-threshold 0.8125 data ())
```

查看 哈希表章节，以获得更多的信息。


# 2.4.13 函数类型

和其他编程语言中的函数类似，Lisp 函数是一段可执行代码。不过和大多数语言不一样的是，Lisp 中的函数也是对象。在 Lisp 中的非编译函数是一个 lambda 表达式。lambda 函数是一个列表，但其第一个元素是符号 lambda。

在大多数的编程语言中，函数必须要有名字。但在 lisp 中，函数并不一定要有名称。一个lambda函数即使没有名字，也可以被称为函数；为了特地强调这一点，我们称这种函数为匿名函数。而具名函数仅仅是将一个合法的匿名函数和符号的函数槽绑定在一起的产物。

大多数时候，函数都可以通过它们的名字进行调用。不过，当你在运行时动态构造、获取，或者调用一个函数对象时，必须要使用基础的 funcall 和 apply 函数进行调用。


# 2.4.14 宏类型

Lisp 宏是用户定义的控制结构。宏可以极大的拓展 Lisp 语言。宏由类似表示函数的对象表示。但是，宏的参数传递语法和函数不同。Lisp 宏是一个列表，其列表的第一个元素是符号macro，然后余下的 CDR 部分是一个 lisp 函数对象，其中包含了符号 lambda。

lisp宏 对象通常由内置的 defmacro 来定义，不过任何以 macro 开头的列表都是宏，在Emacs中也一样。查阅 宏章节，以获取如何编写宏的只是。

警告：Lisp 宏 和 按键宏 完全是两码事。当我们说到宏的时候，我们通常是在说 Lisp宏，而不是按键宏。


# 2.4.15 原函数类型

原函数是指由Lisp调用，但是是用 C 编写的函数。原函数也称作为 subr 或者 内置函数 （built-in functions）。（单词 "subr" 源自 "subroutine"--子例程。）绝大多数的原函数被调用时，会先对参数进行求值。而余下的函数被称为特殊表达式（special form）。特殊表达式在调用时，不会对其参数求值。

当调用函数时，函数是不是原函数并不重要。不过，如果你尝试用lisp代码重定义函数时，这点就很重要。原因是，原函数的调用很可能会绕过Lisp，而直接调用 C 中的代码，这样你的重定义可能会失效。因此我们非常不建议原函数的重定义。

术语 函数（function）单纯指Emacs中的所有函数，而不区分Lisp函数和C函数。详情查阅函数类型章节）。

原函数没有读取语法，其打印表示采用哈希表示，其记号是子例程的名称。

```
(symbol-function 'car)        ; 获取符号的函数槽
    => #<subr car>
(subrp (symbol-function 'car)) ; 这是原函数吗？
    => t                       ; 是。
```


# 2.4.16 字节码类型

字节码函数对象（Byte-code function object）由Lisp编译生成（详情查阅字节编译章节）。内部实现来说，字节码函数和向量很像。详细查阅 字节码对象。

字节码函数对象的打印表示和读取语法和向量比较像，但是在 "#" 前有一个 "\[" 。


# 2.4.17 记录类型

记录（record）和向量（vector）很像。不同的是，记录的第一个元素保存了其类型，会作为函数 type-of 的调用结果返回。记录的作用通常是方便程序员创建Emacs中不存在的新类型的对象。

查阅记录章节，获取更多相关的知识。


# 2.4.18 类型描述符

类型描述符（type descriptor）是一种记录(record)，其中描述了某个类型的信息。记录的第一个槽是一个符号，这个符号表示了这个新类型的名称。而type-of 函数就是从这里获得的类型的信息。余下的槽都不被 Emacs 使用；它们可以用来拓展 Lisp。

类型描述符是 cl-sreucture-class 的实例。


# 2.4.19 自动加载类型

自动加载对象是一个列表，其列表的第一个元素是符号 autoload。余下部分为符号，在符号的函数槽内，其中储存了真实定义的具体位置。自动加载对象会检查定义是否真的存在，并会在必要的时候加载。它包含了文件的名字，以及其他和真实定义相关的信息。

当文件被加载后，这个符号应当拥有一个新的，除自动加载对象外的函数定义。而这个新的定义就好像这个符号本来就是一样。在使用者看来，这个函数调用时应当和预期一样，使用被加载文件中的定义。

自动加载对象通常由函数 autoload 定义，这个函数会创建对象，并把它保存在符号的函数槽内。更多细节请查阅自动加载。


# 2.4.20 终止类型

终止类型对象会协助lisp 代码做一些清理工作，尤其是当某个对象不在被需要的时候。终止对象会储存一个函数对象。在垃圾清理后，终止对象不被任何方式引用时，Emacs会调用终止对象相连的函数对象。在计算引用时，Emacs不会记录终止对象的引用，因此你可以放心的使用终止对象。

终止对象引发的错误会被输出到 \*Messages\* 中。Emacs只会运行一次终止对象相连的函数，即便函数调用失败。

Function: make-finalizer function\
&#x20; 生成一个终止对象，这个终止对象的关联函数为 function。当终止对象不可用时，其关联的函数便会被调用。如果一个终止对象引用了另一个终止对象，那么这个引用不会被计数。每个终止对象只会运行一次关联函数。


# 2.5 编辑类型

前面一章中的类型是通用的编程类型，在大多数的lisp方言中都有。接下来要介绍的是 Emacs Lisp 独有的，专门用来文本数据处理用的类型。


# 2.5.1 缓冲区变量

缓冲区（buffer）是一个 lisp 对象。这个对象中包含了将要被编辑的文本。绝大多数的缓冲区中的内容来自硬盘中的文件，因此可以被编辑。不过也有一些缓冲区另有他用。绝大多数的缓冲区对用户而言都是可见的，因此有时它们可以放在一个窗口中。不过这并不是必要的，缓冲区不需要一定被用户看到。每个缓冲区都有一个特定的位置，这个位置称为编辑点（point）；大多数的编辑命令，都是作用在当前缓冲区编辑点的附近的。不管任何时候，有且只有一个缓冲区称为当前缓冲区。

缓冲区的内容大多情况看起来都很像一个字符串，不过，这些字符串和 Emacs Lisp 中的字符串的用法可不一样。而且不同的缓冲区中，允许的操作也是可能不同的。举个例子，你可以像很高效地在当前缓冲区中插入文字，修改缓冲区的内容。当你插入一个字符串后，Emacs 会将其作为自字符串和原字符串拼接，并返回一整个新的字符串作为结果，展示在缓冲区内。

Emacs 中有很多标准函数，用来修改，测试当前缓冲区内的字符。本文档中有一整章的篇幅来描述这些函数。（详情查阅 文本章）

每个缓冲区变量都有几个关联的数据结构：

* 一个局部语法表
* 一个局部的按键映射
* 一个缓冲区局部变量绑定的列表
* 重载
* 缓冲区中的文本的文本属性

局部按键绑定和局部变量绑定列表中包含许多个性化的，对全局变量的重写。这些变量一般用来自定义不同缓冲区中的不同表现。也就是说，通过重载这些变量，来修改程序（Emcas）的行为，而不需要触及程序的源码。

此外，缓冲区也可以是间接的。这意味着，这个缓冲区和另一个缓冲区共享文本，但是以不同的方式显示这些文本。详情查阅 简介缓冲区章节。

缓冲区没有读取语法，它们以哈希表示打印，其中包含这个缓冲区的名称。

```
(current-buffer)
    => #<buffer objects.texi>
```


# 2.5.2 标记类型

缓冲区中的位置

标记用来表示某个特定缓冲区的位置。因此标记由两部分组成：一部分储存缓冲区信息，一部分储存位置信息。缓冲区的文本变化会自动调节所有相关的标记的位置，以尽可能保证标记的位置始终在某两个固定的字符之间。

标记没有读取语法，它们的打印表示采用哈希记号，其中指明了当前位置和所在的缓冲区。

```
(point-maker)
    => #<marker at 10779 in objects.texi>
```

查阅标记章节，以获得更多和测试、创建、拷贝以及移动标记的信息。


# 2.5.3 窗口类型

窗口描述了 Emacs 终端是如何划分，以显示缓冲区的信息。每个窗口都关联一个缓冲区，而这个缓冲区的内容会在这个窗口中显示。从缓冲区的角度看，缓冲区可能和一个窗口相关联，也可以不和窗口关联，也可以和数个窗口相关联。

在某一时间可能会有多个窗口，但只会有一个被选择的窗口（selected window）。这个窗口通常是光标（cursor）所在的窗口。被选择的窗口通常都显示一个当前的缓冲区（current buffer），但并不总是这样。

屏幕里的窗口通常被框架分成一批一批；每个窗口都必须属于某个框架，而且只能属于一个框架。详情查阅框架类型。

窗口类型没读取语法。它们的打印表示采用哈希记号。其中指明了窗口的编号和被显示的缓冲区。窗口编号对于窗口而言是唯一的，以便区分。而缓冲区可能会经常在不同窗口间反复横跳。

```
(selected-window)
    => #<window 1 on objects.texi>
```

详情查阅窗口章节，以获取更多处理窗口的函数信息。


# 2.5.4 框架类型

框架是指包含一个或更多窗口的屏幕显示区域；我们也使用属于 "frame" 来描述指代一些lisp对象，而这些对象被Emacs用来指代屏幕区域。

框架没有读取语法。它们的打印显示采用哈希记号，其中包含框架的名称，以及内存地址（用来区分不同的框架）。

```
(selected-frame)
    => #<frame emacs@psilocin.gnu.org 0xdac80>
```

查阅 框架章节，以获得更多处理框架的函数信息。


# 2.5.5 终端类型

终端指指调用显示一个或若干个 Emacs 框架的设备（详情查阅框架章节）。

终端类型没有读取语法。它们的打印表示采用哈希记法，其中指明了终端的编号和 TTY 设备的文件名。

```
(get-device-terminal nil)
    => #<terminal 1 on /dev/tty>
```


# 2.5.6 窗口配置类型

窗口配置保存了窗口在框架中的位置，大小，以及内容信息，以便你下次能创建相同的窗口。

窗口配置没有读取语法；它们的打印表示看起来像 "#\<window-configuration>"。详情查阅窗口配置章节，以获取更多处理窗口配置的函数。


# 2.5.7 框架配置类型

框架配置储存了所有框架中窗口的位置，大小，和内容信息。这并不是一个原类型（primitive type）——它是一个列表，其CAR槽为 frame-configuration 而 CDR 槽是一个关联列表。每个关联元素的CAR槽描述了一个框架。

查阅框架配置章节，以获取更多关于框架配置的函数信息。


# 2.5.8 进程类型

术语进程通常指一个运行中的程序。Emacs本身就是一系列进程的有序集合。但在 Emacs Lisp 中，进程是一个用来指代 由 Emacs 进程创建的子进程 的对象。在 Emacs 中作为子进程运行的 shell，GDB，ftp以及编译器，都极大的拓展了 Emacs的能力。Emacs 子进程会从 Emacs 中获取文本输入，并将输出以文本的形式输出到 Emacs，以用来后续操作。此外，Emacs 也可以向它的子进程传递信号。

进程对象没有读取语法。它们的打印表示采用哈希记法，其中包含了进程的名称：

```
(process-list)
    => (#<process shell>)
```

查阅进程章节，以获得更多和进程创建、删除、获取元信息、输入、信号传递以及获取输出相关的知识。


# 2.5.9 线程类型

Emacs 中的线程表示了 Emacs Lisp 执行的一个线程。它运行它自己的 Lisp 程序，拥有自己的 缓冲区，并且拥有一个锁定的 子进程，也就是说，这个子进程的输出只会被这个线程所接收。详情查阅 线程章节。

线程对象没有读取语法。它们的打印表示采用哈希记法，其中包含了 线程名（如果有的话）或其内存地址：

```
(all-threads)
    => (#<thread 0176fc40>)
```


# 2.5.10 互斥锁类型

互斥锁是线程间同步用的对象。详情查阅互斥锁章节。

互斥锁对象没有读取语法。它们的打印表示采用哈希记法，其中包含互斥锁的名称（如果有的话）以及它们在核心中的地址：

```
(make-mutex "my-mitex")
    => #<mutex my-mutex>
(make-mutex)
    => #<mutex 01c7e4e0>
```


# 2.5.11 条件变量类型

条件变量是一个设备，这个设备提供了更复杂的线程同步控制。线程可以在一个条件变量处阻塞，直到其他线程通知这个条件变量。

条件变量没有读取语法。它们的打印表示才有哈希记法，其中包含了条件变量的名称（如果有的话）或者其在核心内的地址：

```
(make-condition-variable (make-mutex))
    => #<condvar 01c45ae8>
```


# 2.5.12 流类型

流是一个对象，这个对象用于传递字符串——既可以作为输出源也可以作为输入源。很多其他不同的类型可以和流进行配合使用。比如：标记、缓冲区、字符串以及函数。通常，输入流（字符源）从键盘、缓冲区、文件接收字符；输出流则向缓冲区，比如 \*Help\* 缓冲区，或者其他位置发送字符。

对象 nil 在某些情况下，也可能表示一个流。nil 经常作为 变量 standard-input 或 变量 standard-output 的值。此外 对象 t 表示向 mini  buffer 传入流或者向 回显区域穿出流。

流没有特殊的打印表示或读取语法，其打印表示为基础类型。

查阅读取和打印，以获取更过和流相关的函数信息，其中包括了解析函数和打印函数。


# 2.5.13 按键映射类型

按键映射将一个按键映射到命令上。按键映射本质上是一个列表。其 CAR 槽为符号 keymap。

查阅按键映射章节，以获取更多创建、调整前缀按键、局部映射、全局映射，以及按键映射的修改相关的信息。


# 2.5.14 覆盖类型

覆盖描述了某个缓冲区那些属性被覆盖。每个覆盖对象都作用在缓冲区属性的某个子集。覆盖对象包含了某个缓冲区的某个范围，以及一个属性列表。覆盖属性会暂时的在某个范围内发挥作用。覆盖对象没有读取语法，其打印表示采用哈希记法，其中包含了缓冲区名字和位置范围。

查阅覆盖，以获取更多关于创建和使用覆盖的信息。


# 2.5.15 字体类型

字体指明了可视化终端中字体的显示方式。通常可以分为三种字体类型——字体对象，字体TODO，字体实体——它们之间有些区差别。它们都没有读取语法；它们的打印表示看起来分别是 "#\<font-object>"，"#\<font-spec>"，和 "\<font-entity>"。查阅底层字体，以获取这三者的相关信息。


# 2.6 循环结构对象

要在 Lisp 对象中表示共享或循环结构，您可以使用读取器结构 '# n =' 和 '# n #'。

在对象前使用#n= 可以标记这个对象，方便后续引用;这样，您可以在另一个地方引用同一个对象。这里，n是某个整数。例如，这里创建了一个列表，其中第一个元素同时作为第三个元素重复出现：&#x20;

```
(#1=(a) b #1#)
```

这不同于像这样的普通语法

```
((a) b (a))
```

这将导致列表的第一个和第三个元素看起来相似，但不是同一个 Lisp 对象。下面这串代码揭示两者的区别：

```
(prog1 nil
  (setq x '(#1=(a) b #1#)))
(eq (nth 0 x) (nth 2 x))
     ⇒ t
(setq x '((a) b (a)))
(eq (nth 0 x) (nth 2 x))
     ⇒ nil
```

您还可以使用相同的语法来创建一个循环结构，该结构看起来像个克莱因瓶。下面是一个例子：

```
#1=(#1#)
```

这将生成一个列表，其第二个元素是列表本身。您可以通过以下代码看到它确实有效：

```
(prog1 nil
  (setq x '#1=(a #1#)))
(eq x (cadr x))
     ⇒ t
```

如果给变量绑定`print-circle` 到非`nil`值，Lisp 打印 可以生成此语法来记录 Lisp 对象中的循环和共享结构。请参阅[输出变量](https://www.gnu.org/software/emacs/manual/html_node/elisp/Output-Variables.html)。\ <br>


# 2.7 类型谓语

当函数被调用时，Emacs Lisp 解释器本身不会对传递给函数的实际参数执行类型检查。它不能这样做，因为 Lisp 中的函数参数没有像其他编程语言中那样的数据类型声明。因此，存在一些函数来测试每个实际参数是否属于该函数可以使用的类型。

所有内置函数都会在适当的时候检查其实际参数的类型，如果参数类型错误，则会发出`wrong-type-argument`错误信号。例如，如果您将`+`无法处理的参数传递给它，则会发生以下情况：

```
(+ 2 'a)
     error→ Wrong type argument: number-or-marker-p, a
```

如果您希望程序以不同方式处理不同类型，则必须进行显式类型检查。检查对象类型的最常见方法是调用*类型谓词*函数。Emacs 对每种类型都有一个类型谓词，还有一些组合类型的谓词。

类型谓词函数接受一个参数；如果参数属于适当的类型，则返回`t`，否则返回`nil`。遵循 Lisp 谓词函数的一般约定，大多数类型谓词的名称以‘p’结尾。

这是一个使用谓词`listp`检查列表和`symbolp`检查符号的示例。

```
(defun add-on (x)
  (cond ((symbolp x)
         ;; 如果 X 是一个符号，将它放入 LIST 。
         (setq list (cons x list)))
        ((listp x)
         ;; 如果 X 是一个列表，则将其元素添加到 LIST。
         (setq list (append x list)))
        (t
         ;; 我们只处理符号和列表。
         (error "Invalid argument %s in add-on" x))))
```

这是一个按字母顺序排列的预定义类型谓词表。

参考 atom. atom

参考 arrayp. arrayp

参考 floatp. bignump

参考 bool-vector-p. bool-vector-p

参考 booleanp. booleanp

参考 bufferp. bufferp

参考 byte-code-function-p. byte-code-function-p

参考 case-table-p. case-table-p

参考 char-or-string-p. char-or-string-p

参考 char-table-p. char-table-p

参考 commandp. commandp

参考 condition-variable-p. condition-variable-p

参考 consp. consp

参考 custom-variable-p. custom-variable-p

参考 floatp. fixnump

参考 floatp. floatp

参考 Low-Level Font. fontp

参考 frame-configuration-p. frame-configuration-p

参考 frame-live-p. frame-live-p

参考 framep. framep

参考 functionp. functionp

参考 hash-table-p. hash-table-p

参考 integer-or-marker-p. integer-or-marker-p

参考 integerp. integerp

参考 keymapp. keymapp

参考 Constant Variables. keywordp

参考 listp. listp

参考 markerp. markerp

参考 mutexp. mutexp

参考 nlistp. nlistp

参考 number-or-marker-p. number-or-marker-p

参考 numberp. numberp

参考 overlayp. overlayp

参考 processp. processp

参考 recordp. recordp

参考 sequencep. sequencep

参考 string-or-null-p. string-or-null-p

参考 stringp. stringp

参考 subrp. subrp

参考 symbolp. symbolp

参考 syntax-table-p. syntax-table-p

参考 threadp. threadp

参考 vectorp. vectorp

参考 wholenump. wholenump

参考 window-configuration-p. window-configuration-p

参考 window-live-p. window-live-p

参考 windowp. windowp

检查对象类型的最通用方法是调用函数`type-of`。回想一下，每个对象都属于一种且只有一种原始类型；`type-of`告诉你是哪一种（参见[Lisp 数据类型](https://www.gnu.org/software/emacs/manual/html_node/elisp/Lisp-Data-Types.html)）。但`type-of`对非原始类型一无所知。在大多数情况下，使用类型谓词比使用`type-of`更方便 。

Function: type-of object

该函数返回一个对象原始类型的符号 。该值是以下符号中的一个`bool-vector`， `buffer`，`char-table`，`compiled-function`， `condition-variable`，`cons`，`finalizer`， `float`，`font-entity`，`font-object`， `font-spec`，`frame`，`hash-table`，`integer`， `marker`，`mutex`，`overlay`，`process`， `string`，`subr`，`symbol`，`thread`， `vector`，`window`，或`window-configuration`。但是，如果object是记录，则返回其第一个槽指定的类型；[记录](https://www.gnu.org/software/emacs/manual/html_node/elisp/Records.html)。

```
(type-of 1)
     ⇒ integer
(type-of 'nil)
     ⇒ symbol
(type-of '())    ; () is nil.
     ⇒ symbol
(type-of '(x))
     ⇒ cons
(type-of (record 'foo))
     ⇒ foo
```


# 2.8 相等谓语

这里我们描述测试两个对象之间相等性的函数。其他函数测试特定类型对象（例如字符串）之间内容的相等性。对于这些谓词，请参阅描述数据类型的相应章节。

Function: **eq** *object1 object2*

如果object1和object2是同一个对象，则此函数返回`t`，否则返回`nil`。

如果object1和object2是同名的符号，它们通常是同一个对象——但请参阅[创建符号](https://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Symbols.html)以了解例外情况。对于其他非数字类型（例如，列表、向量、字符串），具有相同内容或元素的两个参数不一定相互`eq`：当且仅当它们是同一个对象时，它们才`eq`，这意味着其中一个对象的内容发生变化时，相同的变化会体现在另一个对象的内容上。

如果object1和object2是具有不同类型或值的数字，则它们不可能是同一个对象，返回`nil`。如果它们是具有相同值的 fixnum，则它们是相同的对象并返回`t`。如果它们是单独计算的，但碰巧具有相同的值和相同的数字类型，那么它们可能是也可能不是同一个对象，返回`t`或`nil` 取决于 Lisp 解释器创建了一个还是两个对象。

```
(eq 'foo 'foo)
     ⇒ t

(eq ?A ?A)
     ⇒ t

(eq 3.0 3.0)
     ⇒ t or nil
;; Equal floats may or may not be the same object.

(eq (make-string 3 ?A) (make-string 3 ?A))
     ⇒ nil

(eq "asdf" "asdf")
     ⇒ t or nil
;; Equal string constants or may not be the same object.

(eq '(1 (2 (3))) '(1 (2 (3))))
     ⇒ nil

(setq foo '(1 (2 (3))))
     ⇒ (1 (2 (3)))
(eq foo foo)
     ⇒ t
(eq foo '(1 (2 (3))))
     ⇒ nil

(eq [(1 2) 3] [(1 2) 3])
     ⇒ nil

(eq (point-marker) (point-marker))
     ⇒ nil
```

该`make-symbol`函数返回一个未收录符号，与在 Lisp 表达式中写入名称时使用的符号不同。此时具有相同名称的不同符号并不`eq`。请参阅[创建符号](https://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Symbols.html)。

```
(eq (make-symbol "foo") 'foo)
     ⇒ nil
```

Emacs Lisp 字节编译器可能会将相同的文字对象（例如文字字符串）折叠为对同一对象的引用，但解释器可能并不会这么做。因此，你的代码永远不要使用 eq 去比较两者的文本意义，你应该使用 equal 函数去比较文本意义。

Function: equal object1 object2

如果object1和object2具有相同的组件，则此函数返回`t`，否则返回`nil`。`eq`测试它的参数是否是同一个对象，而`equal`查看不相同参数的元素或内容是否相同。因此，如果两个对象是`eq`，则它们是`equal`，但反过来并不总是正确的。

```
(equal 'foo 'foo)
     ⇒ t

(equal 456 456)
     ⇒ t

(equal "asdf" "asdf")
     ⇒ t
(eq "asdf" "asdf")
     ⇒ nil

(equal '(1 (2 (3))) '(1 (2 (3))))
     ⇒ t
(eq '(1 (2 (3))) '(1 (2 (3))))
     ⇒ nil

(equal [(1 2) 3] [(1 2) 3])
     ⇒ t
(eq [(1 2) 3] [(1 2) 3])
     ⇒ nil

(equal (point-marker) (point-marker))
     ⇒ t

(eq (point-marker) (point-marker))
     ⇒ nil
```

字符串的比较区分大小写，但不考虑文本属性——它只比较字符串中的字符。请参阅[文本属性](https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Properties.html)。使用`equal-including-properties`也比较文本属性。出于技术原因，当且仅当单字节字符串和多字节字符串包含相同的字符代码序列，并且所有这些代码都在 0 到 127 ( ASCII )范围内时，它们才`equal`。

```
(equal "asdf" "ASDF")
     ⇒ nil
```

但是，两个不同的缓冲区从不`equal`，即使它们的文本内容相同。

对于`equal`，相等是递归定义的；例如，给定两个节点X和Y， 当且仅当下面两个表达式同时返回`t`时返回`t`。

```
(equal (car x) (car y))
(equal (cdr x) (cdr y))
```

因此，比较循环列表可能会导致导致错误的深度递归，这可能会导致违反直觉的行为，例如 `(equal a b)`返回`t`而`(equal b a)` 发出错误信号。

Function: equal-including-propertied object1 object2

此函数在所有情况下的行为都类似`equal`，但此外，若要求两个字符串相等，那么它们具有的文本属性也必须相同。

```
(equal "asdf" (propertize "asdf" 'asdf t))
     ⇒ t
(equal-including-properties "asdf"
                            (propertize "asdf" 'asdf t))
     ⇒ nil
```


# 2.9 可变性

一些 Lisp 对象永远不应该改变其内容。例如，Lisp 表达式`"aaa"`生成一个字符串，但您不应更改其内容。有些对象是不可变的；例如，尽管您可以通过计算 1 来创建一个新数字，但 Lisp 不提供更改现有数字值的操作。

其他 Lisp 对象是*可变的*：通过具有副作用的破坏性操作来更改它们的值是安全且被允许的。例如，可以通过将标记移动到其他位置来更改现有标记。

数字永远是不可变的，并且所有标记都是可变的。此外，某些类型具有若干成员，其中一些成员是可变的，而另一些则不是。这些类型包括点对、向量和字符串。例如，虽然`"cons"`和`(symbol-name 'cons)`都生成不可变的字符串，但`(copy-sequence "cons")`和 `(make-string 3 ?a)`都生成可以通过 aset 改变的可变字符串。

如果可变对象是被求值的表达式的一部分，那它就不再是可变的。例如：

```
(let* ((x (list 0.5))
       (y (eval (list 'quote x))))
  (setcar x 1.5) ;; 程序不应该做这样的事情
  y)
```

尽管该列表`(0.5)`在创建时是可变的，但它不应通过 `setcar` 更改，因为它已提供给`eval`用于求值。 相反的情况则不会发生：不可变的对象在此之后永远不会变得可变。

如果程序试图更改不可变对象，则结果行为是未定义的：Lisp 解释器可能会抛出错误信号，或者它可能会崩溃或出现其他方式无法预测的反应。[2](https://www.gnu.org/software/emacs/manual/html_node/elisp/Mutability.html#FOOT2)

当类似的常量作为程序的一部分出现时，Lisp 解释器可能会通过重用现有的常量或其组件来节省时间或空间。例如，在例子`(eq "abc" "abc")`中，如果解释器只创建一个字符串常量的实例`"abc"`返回 `t`，如果它创建两个实例，则返回`nil`。应该编写这样的 Lisp 程序，以便无论是否使用这种优化，它们都可以工作。

**脚注**

**(2)**

这是为 Common Lisp 和 C 等语言指定的常量行为，这与 JavaScript 和 Python 等语言不同，如果程序试图更改不可变对象，则需要解释器来发出错误信号。理想情况下，Emacs Lisp 解释器将会在后续版本中覆盖这一问题。


# 3.1 整型数

整数的表示和范围。

Lisp 读取器读取一个整数并解释为十进制数字。整数可以带有可选的初始符号和可选的结尾句点。

```
1 ; 整数 1. 
1. ; 整数 1. 
+1 ; 也是整数 1. 
-1 ; 整数 -1。
0; 整数 0. 
-0 ; 整数 0。
```

以 10 为基数的整数的读取语法为 '#' 后跟一个基数指示，后跟一位或多位数字。'b'对应二进制，'o'对应八进制，'x' 对应十六进制，和 '基数r' 对应自定义基数。因此， '#b整数' 读取 二进制整数，并且 '#基数r整数' 读取自定义基数对应的的整数。基数的合法值从 2 到 36，允许的数字覆盖字符'0'——'9', 'a'——'Z'。字母大小写被忽略，切没有首字母或句号。例如：

```
#b101100 ⇒ 44
#o54 ⇒ 44
#x2c ⇒ 44
#24r1k ⇒ 44
```

要了解各种函数如何处理整数，尤其是按位运算符（请参阅[按位运算](https://www.gnu.org/software/emacs/manual/html_node/elisp/Bitwise-Operations.html)），查看二进制形式的数字通常很有帮助。

在二进制中，十进制整数 5 如下所示：

```
…000101
```

（省略号 '…' 代表与前导位匹配的概念上无限数量的位；在这里，无限数量的 0 位。后面的例子也使用这个 '…' 符号。）

整数 -1 如下所示：

```
…111111
```

-1 表示为全 1。（这称为*二进制补码*表示法。）

从 -1 中减去 4 返回负整数 -5。在二进制中，十进制整数 4 是 100。因此，-5 看起来像这样：

```
…111011
```

本章中描述的许多函数都接受标记作为参数，来代替数字。（请参阅[Markers](https://www.gnu.org/software/emacs/manual/html_node/elisp/Markers.html)。）由于此类函数的实际参数可能是数字或标记，因此我们通常将这些参数命名为number-or-marker。当参数值为标记时，使用其位置值并忽略其缓冲区。

在 Emacs Lisp 中，文本字符由整数表示。零和 `(max-char)`的值之间的任何整数（包括）都被认为是有效的字符。请参阅[字符代码](https://www.gnu.org/software/emacs/manual/html_node/elisp/Character-Codes.html)。

Emacs Lisp 中的整数范围不受机器字节大小的限制。但是，在黑箱内部，有两种整数：较小的整数，称为*fixnums*，较大的整数，称为*bignums*。尽管 Emacs Lisp 代码通常不应该依赖于整数是 fixnum 还是 bignum，但较旧的 Emacs 版本仅支持 fixnum，Emacs 中的某些函数仍然只接受 fixnum，并且较旧的 Emacs Lisp 代码在给定 bignum 时可能会出现问题。例如，而旧的Emacs Lisp代码可以安全地用`eq`来比较数字，而新的Emacs中的bignums 应当使用类似eql 和 = 这样的谓词进行整数比较。

bignums 的值范围受主内存量、机器特征（例如用于表示 bignum 指数的单词的大小）以及`integer-width` 变量的限制。这些限制通常比 fixnum 的限制大得多。bignum 在数值上永远不等于 fixnum；Emacs 总是将 fixnum 范围内的整数表示为 fixnum，而不是 bignum。

fixnum 的值范围取决于机器。最小范围是 -536,870,912 到 536,870,911（30 位；即 -2\*\*29 到 2\*\*29 - 1），但许多机器提供更广泛的范围。

Variable: **most-positive-fixnum**

这个变量的值是 Emacs Lisp 可以处理的最大“小”整数。在 32 位平台上通常为 2\*\*29 - 1，在 64 位平台上为 2\*\*61 - 1。

Variable: **most-negative-fixnum**

这个变量的值是 Emacs Lisp 可以处理的数字最小的“小”整数。它是负面的。在 32 位平台上为 -2\*\*29，在 64 位平台上为 -2\*\*61。

Variable：integer-width

此变量的值是一个非负整数，用于控制 Emacs 在计算大整数时是否抛出范围错误。绝对值小于 2\*\* n 的整数（其中n是此变量的值）不会抛出范围错误。尝试创建更大的整数通常会引发范围错误，尽管廉价地创建更大的整数，则可能不会抛出错误。如果计算产生巨大的整数，则将此变量设置为一个大的数字可能代价会高昂。


# 3.2 浮点型基础

浮点数对于表示非整数的数字很有用。浮点数的范围与您使用的机器上的C double数据类型的范围相同。在 所有支持 Emacs 的计算机上，这是 IEEE binary64 浮点格式，由IEEE Std 754-2019标准化， 并在 David Goldberg 的论文“ What Every Computer Scientist Should Know About Floating-Point Arithmetic ”中进一步讨论。在现代平台上，浮点运算严格遵循 IEEE-754 标准；然而，在一些过时的平台上，特别是 32 位 x86，结果并不总是正确四舍五入。

浮点数的读取语法需要小数点、指数或两者。 在数字及其指数之前可加 可选符号 ('+' 或者 '-')。例如， '1500.0’, ‘+15e2’, ‘15.0e + 2’, ‘+1500000e-3'， 和 '.15e4' 是值为 1500 的浮点数的五种表示方式。它们都是等价的。与 Common Lisp 一样，Emacs Lisp 要求浮点数中的任何小数点后至少有一位：'1500.' 是整数，而不是浮点数。

Emacs Lisp 将 -0.0 和 普通的 0 视为等价，这种等价是基于 = 比较。这个规定符合 IEEE 浮点数标准，这个标准指明 -0.0 和 0.0 在数值层面是相等的，即便有些操作符可能会区别它们。

IEEE浮点标准支持正无穷大与负无穷大浮点值。它还提供了一类称为 NaN 或“非数字”的值；在没有正确答案的情况下，数值函数会返回此类值。例如，(/ 0.0 0.0)返回 NaN。NaN 在数值上永远不等于任何值，甚至不等于它本身。NaN 带有一个符号和一个有效数，当它们的符号和有效数一致时，非数值函数将两个 NaN 视为相等。NaN 的有效数依赖于机器。

当NaN和符号的零参与，非数字功能，如 eql，equal，sxhash-eql，sxhash-equal并 gethash确定值是否是无法区分的，他们不是是否数值上相等。例如，当x和 y是相同的 NaN 时，(equal x y)返回t而 (= x y)使用数值比较并返回nil；相反，(equal 0.0 -0.0)返回nil而 (= 0.0 -0.0)返回t。

以下是这些特殊浮点值的读取语法：

无限大 ‘1.0e+INF' 和 '-1.0e+INF’

非数字 ‘0.0e + NaN' 和 '-0.0e + NaN’

以下函数用于专门处理浮点数：

Function：isnan x 如果其浮点参数为 NaN，则此谓词返回 t， 否则返回 nil。

Function：frexp x 此函数返回一个 cons cell (s . e)，其中s和e分别是浮点数x的有效数和指数。

如果 x 是有限的，则 s 是介于 0.5（含）到 1.0（不含）之间的浮点数，e 是整数，并且 x = s \* 2 \*\* e。如果 x 为零或无穷大，则 s 与 x 相同。如果 x 是 NaN，则 s 也是 NaN。如果 x 为零，则 e 为 0。

Function：ldexp s e 给定数字有效数 s 和整数指数 e ，此函数返回浮点数 s \* 2 \*\* e。

Function：copysign x1 x2 此函数将 x2 的符号复制到 x1 的值，并返回结果。 x1 和 x2 必须是浮点数。

Function：logb x 此函数返回 x 的二进制指数。更准确地说，如果 x 是有限且非零的，则该值是 |x| 的以 2 为底的对数 , 并向下舍入为整数。如果 x 为零或无穷大，则值为无穷大；如果 x 是 NaN，则该值是 NaN。

(logb 10) ⇒ 3 (logb 10.0e20) ⇒ 69 (logb 0) ⇒ -1.0e+INF


# 3.3 谓词：数字相关

本节中的函数测试数字或特定类型的数字。函数 integerp 和 floatp 可以接受任何类型的 Lisp 对象作为参数（否则它们不会有太大用处），但 zerop 谓词需要一个数字作为参数。另见 integer-or-marker-p 并 number-or-marker-p 在标记谓词。

Function：bignump object 这个谓词测试它的参数是否是一个大整数，如果是，则返回 t，否则返回 nil。与小整数不同，大整数可以是=或者eql即使不是eq。

Function：fixnump object 这个谓词测试它的参数是否是一个小整数，如果是，则返回 t，否则返回 nil。小整数可以与 进行比较eq。

Function：floatp object 这个谓词测试它的参数是否是浮点数，如果是，则返回 t，否则返回 nil。

Function：integerp object 这个谓词测试它的参数是否是一个整数，如果是则返回 t，否则返回 nil 。

Function：numberp object 这个谓词测试它的参数是否是一个数字（整数或浮点数），如果是，则返回 t，否则返回 nil。

Function：natnump object 这个谓词（它的名字来自短语“natural number”）测试它的参数是否是一个非负整数，如果是，则返回 t，否则返回 nil。0 被认为是非负数。

wholenump 是 natnump 的同义词。

Function：zerop number 这个谓词测试它的参数是否为零， 如果是，则返回 t，否则返回 nil。参数必须是数字。

(zerop x) 相当于 (= x 0)。


# 3.4 数字比较

要测试数字是否相等，通常应该使用 = 比较, 而非使用非数字比较谓词，如 eq, eql 和 equal。浮点和大整数对象在数值意义上可以相等。如果你使用 eq 来比较它们，你是在测试它们是否是同一个对象；如果使用 eql 或 equal ，则是在测试它们的值是否不可区分。相反，= 是用于数字比较，有时会在非数字比较时返回 t ，反之亦然。请参阅浮动基础知识。

在 Emacs Lisp 中，如果两个 fixnum 在数值上相等，则它们是同一个 Lisp 对象。也就是说，比较 fixnum 时，eq 和 = 是等价的。有时使用 eq 将未知值与 fixnum 进行比较很方便，因为如果未知值不是数字，eq 并不会报告错误——它接受任何类型的参数。相比之下，如果参数不是数字或标记，那么 = 会抛出错误。但是，为了更好的编程实践，请尽量使用 = ，即便是比较整数。

使用 eql 或 equal 比较数字，则会将两个数字比较是否具有相同的数据类型（都是整数，或都是浮点数）和相同的值。相比之下， = 可以将整数和浮点数视为相等。请参阅相等谓词。

还有一个问题：因为浮点运算并不精确，所以检查浮点值的相等性通常是一个坏主意。通常最好使用近似相等测试。这里是一个执行此操作的函数：

(defvar fuzz-factor 1.0e-6) (defun approx-equal (x y) (or (= x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) fuzz-factor)))

Function：= number-or-maker \&rest number-or-makers 此函数测试其所有参数在数值上是否相等，如果相等则返回 t，否则返回 nil。

Function：eql value1 value2 此函数的作用类似于 eq 不同的是，两个参数都因该是数字。它按类型和数值比较数字，因此 (eql 1.0 1)返回nil，但是(eql 1.0 1.0)和 (eql 1 1)都返回 t。这可用于比较大整数和小整数。

Function：/= number-or-marker1 number-or-marker2 此函数测试其参数在数值上是否相等，如果不相等 t，否则返回 nil。

Function：< number-or-maker \&rest number-or-makers 此函数测试每个参数是否严格小于下一个参数。如果是，则返回 t，否则返回 nil。

Function：<= number-or-maker \&rest number-or-makers 此函数测试每个参数是否小于或等于下一个参数。如果是，则返回 t，否则返回 nil。

Function：> number-or-maker \&rest number-or-makers 此函数测试每个参数是否严格大于下一个参数。如果是，则返回 t，否则返回 nil。

Function：>= number-or-maker \&rest number-or-makers 此函数测试每个参数是否大于或等于下一个参数。如果是，则返回 t，否则返回 nil。

Function：max number-or-maker \&rest numbers-or-makers 此函数返回最大的参数。

（最多 20 个） ⇒ 20 （最大 1 2.5） ⇒ 2.5 （最大 1 3 2.5） ⇒ 3 Function：min number-or-maker \&rest numbers-or-makers 此函数返回最小参数。

(最小 -4 1) ⇒ -4 Function：abs number 此函数返回number的绝对值。


# 3.5 数字类型转换

要将整数转换为浮点数，请使用函数float。

Function: float number 这将返回转换为浮点数的数字。如果number已经是浮点数，float则原样返回。

有四个函数可以将浮点数转换为整数；它们的不同之处在于它们如何取整。这些函数都接受参数 number和一个可选的参数 divisor。两个参数都可以是整数或浮点数。 除数也可能是 nil。如果除数是 nil或被省略，那这些函数会将数字转换为整数，如果它已经是整数，则原样返回。如果除数非nil，那么这些函数会将数字除以除数并将结果转换为整数。如果除数为零（无论是整数还是浮点数），Emacs 会抛出 arith-error 错误。

Function: truncate number \&optional divisor 这将返回number，通过向零取整将其转换为整数。

(truncate 1.2) ⇒ 1 (truncate 1.7) ⇒ 1 (truncate -1.2) ⇒ -1 (truncate -1.7) ⇒ -1 Function: floor number \&optional divisor 这将返回number，通过向下舍入（向负无穷大）转换为整数。

如果指定了除数，则使用去余，向下舍入。

(floor 1.2) ⇒ 1 (floor 1.7) ⇒ 1 (floor -1.2) ⇒ -2 (floor -1.7) ⇒ -2 (floor 5.99 3) ⇒ 1

Function: ceiling number \&optional divisor 这将返回number，通过向上舍入（向正无穷大）转换为整数。

(ceiling 1.2) ⇒ 2 (ceiling 1.7) ⇒ 2 (ceiling -1.2) ⇒ -1 (ceiling -1.7) ⇒ -1 Function: round number \&optional divisor 这将返回number，并向最接近的整数四舍五入转换为整数。如果在两个数正中间，则将其舍入偶数整数。

(round 1.2) ⇒ 1 (round 1.7) ⇒ 2 (round -1.2) ⇒ -1 (round -1.7) ⇒ -2


# 3.6 数学变换

Emacs Lisp 提供了传统的四种算术运算（加法、减法、乘法和除法），以及取余和取模函数，以及加减 1 的函数。 除了%，这些函数中的每一个都接受整数和浮点参数, 如果任何参数是浮点数，则返回一个浮点数。

Function：1+ number-or-maker 此函数返回数字或标记加1。例如，

(setq foo 4) ⇒ 4 (1+ foo) ⇒ 5 此函数与 C 运算符++不同——它不增加变量。它只是计算一个总和。因此，如果我们继续，

foo ⇒ 4 如果要增加变量，则必须使用setq，如下所示：

(setq foo (1+ foo)) ⇒ 5

Function：1- number-or-maker 此函数返回数字或标记减 1。

Function：+ \&rest numbers-or-makers 此函数将其参数相加。当没有给定参数时，+ 返回 0。

(+) ⇒ 0 (+ 1) ⇒ 1 (+ 1 2 3 4) ⇒ 10

Function：- \&optional number-or-maker \&rest more-numbers-or-makers 该-函数有两个目的：求反和减法。当-有单个参数时，返回值是参数的负数。当有多个参数时，从number-or-marker中累计减去每个more-numbers-or-markers。如果没有参数，则结果为 0。

(- 10 1 2 3 4) ⇒ 0 (- 10) ⇒ -10 (-) ⇒ 0 Function：\* \&rest numbers-or-makers 此函数将其参数相乘，并返回乘积。当没有给定参数时，\* 返回 1。

(*) ⇒ 1 (* 1) ⇒ 1 (\* 1 2 3 4) ⇒ 24

函数：/ number \&rest divisors 与一个或多个除数，该函数 依次用 divisor 除 number 并且返回商。由于没有除数，此函数返回 1 / number。每个参数可以是数字或标记。

如果所有参数都是整数，则结果是一个整数，在每次除法后将商向零舍入。

(/ 6 2) ⇒ 3 (/ 5 2) ⇒ 2 (/ 5.0 2) ⇒ 2.5 (/ 5 2.0) ⇒ 2.5 (/ 5.0 2.0) ⇒ 2.5 (/ 4.0) ⇒ 0.25 (/ 4) ⇒ 0 (/ 25 3 2) ⇒ 4 (/ -17 6) ⇒ -2 如果将一个整数除以整数 0，Emacs 会抛出 arith-error 错误 （请参阅 Errors）。非零数除以浮点数零会产生正无穷大或负无穷大（请参阅浮点基础知识）。

Function：% dividend divisor 该函数返回相除后的余数。参数必须是整数或标记。

对于任意两个整数 dividend 和 divisor,

(+ (% dividend divisor) (\* (/ dividend divisor) divisor)) 如果 divisor 非零，则结果总是等于 dividend。

(% 9 4) ⇒ 1 (% -9 4) ⇒ -1 (% 9 -4) ⇒ 1 (% -9 -4) ⇒ -1 Function：MOD dividend divisor 此函数取余；

与 不同%，mod允许浮点参数；它将商向下舍入（向负无穷大）为整数，并使用该商计算余数。

如果 divisor 为零， 则在两个参数都是整数时 mod 抛出 arith-error错误，其他情况下返回 NaN。

(mod 9 4) ⇒ 1 (mod -9 4) ⇒ 3 (mod 9 -4) ⇒ -3 (mod -9 -4) ⇒ -1 (mod 5.5 2.5) ⇒ .5 对于任意两个数 dividend 和 divisor，

(+ (mod dividend divisor) (\* (floor dividend divisor) divisor)) 总是等于dividend。如果任一参数是浮点数，则会有舍入误差影响；如果除数为 0，则会抛出 arith-error 错误。有关floor，请参阅数字转换。


# 3.7 取整

函数ffloor、fceiling、fround和 ftruncate接受一个浮点参数并返回一个浮点结果，其值为附近的整数。 ffloor返回向下取整的整数；fceiling返回向上取整的整数； ftruncate, 返回向零方向取整的整数； fround，最接近的整数。

Function：ffloor float 此函数将float 向下舍入取整，并将该值作为浮点数返回。

Function：fceiling float 此函数将float 向上舍入取整，并将该值作为浮点数返回。

Function：ftruncate float 此函数将float 向零舍入取整，并将该值作为浮点数返回。

Function：fround float 此函数将float 舍入到最接近的整数值，并将该值作为浮点数返回。对两个整数之间等距的值，舍入到接近的偶数。


# 3.8 位运算

在计算机中，整数使用二进制数、位序列（零或一的数字）表示。从概念上讲，左边的位序列是无限的，最高有效位全为零或全为 1。按位运算会作用于此类序列的各个位。例如， 位移操作 会将整个序列向左或向右移动一个或多个位置。

Emacs Lisp 中的按位运算仅适用于整数。

Function：ash integer1 count ash(算术移位) 将integer1 中的位向左边移动count 位，如果count为负则向右移动。左移会在右边引入零位；右移则会丢弃最右边的位。视为等价的整数运算， ash将integer1乘以 2 的 count 次方，然后通过向下舍入将结果转换为整数。

以下是ash将位模式向左和向右移动一位的示例。这些示例仅显示二进制模式的低位；前导位都与所示的最高位一致。如您所见，左移一相当于乘以二，而右移一相当于除以二，然后向下舍入取整。

(ash 7 1) ⇒ 14 ;; Decimal 7 becomes decimal 14. …000111 ⇒ …001110

(ash 7 -1) ⇒ 3 …000111 ⇒ …000011

(ash -7 1) ⇒ -14 …111001 ⇒ …110010

(ash -7 -1) ⇒ -4 …111001 ⇒ …111100

以下是向左或向右移动两位的示例：

```
              ;         binary values
```

(ash 5 2) ; 5 = …000101 ⇒ 20 ; = …010100 (ash -5 2) ; -5 = …111011 ⇒ -20 ; = …101100 (ash 5 -2) ⇒ 1 ; = …000001 (ash -5 -2) ⇒ -2 ; = …111110

Function：lsh integer1 count lsh，是logic shift的缩写，将integer1左移count位，如果count为负，则右移，使用 0 补位。如果 count为负，则integer1必须是 fixnum 或正的 bignum。此外，lsh 将 fixnum 连减两次 most-negative-fixnum ，将其处理为为无符号数字。这种古怪的行为可以追溯到 Emacs 只支持 fixnums 的时候。现在使用 ash 是更好的选择。

lsh的行为和ash很像，但当 integer1和 count1均为负时，以下的例子仅出现在这些特殊情况下。这些示例假定使用 30 位 fixnums。

```
             ;      binary values
```

(ash -7 -1) ; -7 = …111111111111111111111111111001 ⇒ -4 ; = …111111111111111111111111111100 (lsh -7 -1) ⇒ 536870908 ; = …011111111111111111111111111100 (ash -5 -2) ; -5 = …111111111111111111111111111011 ⇒ -2 ; = …111111111111111111111111111110 (lsh -5 -2) ⇒ 268435454 ; = …001111111111111111111111111110

Function：logand \&rest ints-or-markers 此函数将参数进行按位与操作。

例如，使用 4 位二进制数，13 和 12 按位 与 结果为 12：1101 与 1100 按位 与 的 1100。在这两个二进制数中，最左边的两位都是 1，因此返回值的最左边的两位都是 1。但是，对于最右边的两位，其中一个参数是 0，因此返回值的最右边两位都是 0。

因此，

（logand 13 12） ⇒ 12 如果logand未传递任何参数，则返回值 -1。这个是一个logand的单位数字，因为它的二进制表示完全由 1 组成。如果只传递一个参数，则返回该参数。

```
               ;        binary values
```

(logand 14 13) ; 14 = …001110 ; 13 = …001101 ⇒ 12 ; 12 = …001100

(logand 14 13 4) ; 14 = …001110 ; 13 = …001101 ; 4 = …000100 ⇒ 4 ; 4 = …000100

(logand) ⇒ -1 ; -1 = …111111

Function：logior \&rest ints-or-markers 该函数对参数进行按位或运算。如果没有参数，则结果为 0，这是此操作的恒定元素。如果只传递一个参数，则返回该参数。

```
               ;        binary values
```

(logior 12 5) ; 12 = …001100 ; 5 = …000101 ⇒ 13 ; 13 = …001101

(logior 12 5 7) ; 12 = …001100 ; 5 = …000101 ; 7 = …000111 ⇒ 15 ; 15 = …001111

Function：logxor \&rest ints-or-markers 此函数将参数按位异或。如果没有参数，则结果为 0，这是此操作的恒定元素。如果只传递一个参数，则返回该参数。

```
               ;        binary values
```

(logxor 12 5) ; 12 = …001100 ; 5 = …000101 ⇒ 9 ; 9 = …001001

(logxor 12 5 7) ; 12 = …001100 ; 5 = …000101 ; 7 = …000111 ⇒ 14 ; 14 = …001110

Function：lognot integer 此函数将参数按位补码。结果和整数 -1 等价。

(lognot 5) ⇒ -6 ;; 5 = …000101 ;; becomes ;; -6 = …111010

Function：logcount integer 该函数返回整数的汉明权重：在那些的二进制表示的数量的整数。如果integer为负数，则返回其二进制补码表示中的零位数。结果总是非负的。

(logcount 43) ; 43 = …000101011 ⇒ 4 (logcount -43) ; -43 = …111010101 ⇒ 3


# 3.9 数学函数

这些数学函数允许整数和浮点数作为参数。

函数：sin arg Function：cos arg Function：tan arg 这些是基本的三角函数，参数arg 以弧度为单位。

Function：asin arg 值是一个介于 -pi/2 和 pi/2（含）之间的数字，其正弦为arg。如果arg超出范围（在 \[-1, 1] 之外），则返回 NaN。

Function：aso arg 值是一个介于 0 和 pi（含）之间的数字，其余弦为arg。如果arg超出范围（在 \[-1, 1] 之外），则返回 NaN。

Function：atan y \&optional x 值是一个介于 -pi/2 和 pi/2（不包括）之间的数字，其正切为y。如果给出了可选的第二个参数x，则值是向量和 轴之间的弧度角。

Function：exp arg 指数函数；它返回e的 arg 次幂。

Function：log arg \&optional base 此函数返回arg的对数，以 base 为 底。如果不指定base，则使用自然基数 e。如果arg或base为负，则返回 NaN。

函数：expt x y 此函数返回x 的y 次幂。如果两个参数都是整数且y为非负数，则结果为整数；在这种情况下，会出现溢出错误，所以要小心。如果x是有限负数且y是有限非整数，则expt返回 NaN。

Function：sqrt arg 返回arg 的平方根。如果arg是有限的且小于零，则sqrt返回 NaN。

此外，Emacs 定义了以下常用的数学常数：

Variable：float-e 数学常数 e (2.71828…)。

Variable：float-pi 数学常数 pi (3.14159…)。


# 3.10 随机数

确定性计算机程序无法生成真正的随机数。但对于大多数用途，伪随机数就足够了。以确定性方式生成一系列伪随机数。这些数字并不是真正随机的，但它们具有随机序列的某些特性。例如，所有可能的值同样在伪随机序列中经常出现。

伪随机数是从种子值生成的。从任何给定的种子开始，该random函数总是生成相同的数字序列。默认情况下，Emacs 会在启动时初始化随机种子，这样random 在每次 Emacs 运行中（具有压倒性的可能性）的值序列都不同。

有时您希望随机数序列是可重复的。例如，在调试一个行为依赖于随机数序列的程序时，在每次程序运行中获得相同的行为是很有帮助的。要使序列重复，请执行(random "")。这会将您的特定 Emacs 可执行文件的种子设置为常量值（尽管其他 Emacs 版本可能会有所不同）。您可以使用其他字符串作为种子值。

Function：random \&optional limit 此函数返回一个伪随机整数。重复调用会返回一系列伪随机整数。

如果limit是一个正的 fixnum，则返回值为非负且小于limit。否则，该值可能是任何 fixnum，即从most-negative-fixnum到 most-positive-fixnum 的任何整数（请参阅Integer Basics）。

如果limit是t，则意味着重新选择一个新的种子，就像 Emacs 重新启动一样，其值通常是从系统熵中选择。在缺少熵池的系统上，从随机性较低的数据（例如当前时间）中选择种子。

如果limit是一个字符串，则表示基于字符串的内容选择一个新的种子。


# 4 字符串和字符

Emacs Lisp 中的 字符串是有序字符序列。通常字符串用来当作符号、缓冲区，以及文件的名称；向用户发信息；传递不同缓冲区的文本；以及其他很多作用。字符串作用非常多，而且重要，因此 Emacs Lisp 提供了非常多的字符串处理函数。Emacs Lisp 中，字符比字符使用的更加频繁。

查阅 Strings of Events，以查看键盘相关的字符串使用方式。


# 4.1 字符串基础

字符和字符串的基础属性

字符是 一个 Lisp 对象，用来表示文本中单独的字符。在 Emacs Lisp 中，字符本质上是整型数；但是一个整型数究竟被当成字符还是整数，这要看其具体用法。关于整型数的字符表示，具体查阅 字符码 章节。

字符串是不变的字符序列。有一种数据类型叫做数组，数组一旦被创建，其长度便固定下来，而且不能再改变（详情查阅数组向量序列章节）。和 C 不同，Emacs Lisp 中的字符串不会被分隔符终止。

字符串是数组，因此也是序列。针对序列和数组的函数，同样可以用来操作字符串（这些函数可以在 序列 数组 向量 章节）。比如，你可以使用针对数组的 aref 函数来获取字符串中的单个字符。

Emacs 中的非ASCII字符有两种文本表示：单字节和多字节。对于大多的 Lisp 编程，你不需要特地区分这两种编码。详情查阅 文本表示章节。

有时，按键序列会以单字节字符串表示。当使用单字节字符串来表示按键序列时，在 128 到 255 区间的字符串元素表示 meta 字符，而不是 128 到 255 的字符码。字符串中无法储存 hyper，super以及alt 控制键；字符串中可以储存 ASCII 控制键，但无法储存其他的控制键。此外 Emacs 并不区分 ASCII 字符控制键的大小写。如果你想要储存这样的字符，比如按键序列，那你就不能使用字符串，而应该使用向量。更多按键输入字符，详情查阅 字符类型章节。

字符串同样被用来储存正则表达式。你可以使用 string-match 对字符串进行正则匹配。函数 match-string 和 replace-match 使用正则表达式对字符串进行分解好修改。

和缓冲区类似，字符串也可以储存具有文本属性的字符。详情查阅文本属性。Lisp 中很多拷贝文本的操作都会连文本属性一并拷贝。

查阅 文本 章节，以获取在缓冲区中展示和拷贝字符串的相关函数。查阅 字符类型和字符串类型章节，以获取关于 字符和字符串的语法表示。查阅 非ASCII字符章节，以获取字符串编码及解码相关的函数。此外要注意，函数 length 并不是用来计算 被显示的文本的长度；而应该使用 string-width函数。


# 4.2 谓语：字符串相关

查阅 序列数组 向量以及向量章节，查阅更多关于底层 通用序列 及 数组 谓语。

Function: stringp object\
&#x20; 如果 object 为字符串，则返回 t，否则返回 nil。

Function: string-or-null-p object\
&#x20; 如果 object 为字符串，或 nil，则返回 t，否则返回 nil。

Function: char-or-string-p object\
&#x20; 如果 object 为字符或字符串，则返回 t，否则返回 nil。


# 4.3 创建字符串

本章将介绍 直接，间接（如拼接，取子字符串）创建字符串的函数。至于通过修改其他字符串内容，从而创建新字符串的方法，请查阅 搜索与替换 章节。

Function: make-string count character & optional multibyte\
&#x20; 这个函数将 character 重复 count 次，并作为字符串返回。如果 count 为负，那么这个函数会抛出错误。

```
(make-string 5 ?x)
    => "xxxxx"
(make-string 0 ?x)
    => ""
```

通常来说，如果一个字符是ASCII字符，那么返回的字符串是单字节字符串。如果可选参数 multibyte 非nil，则函数会返回一个多字节字符串。如果后面，你需要拼接非ASCII字符串或使用非ASCII字符替换其中字符的话，这个这个功能会非常有用。

其他还有些类似的函数，比如 make-vector （查阅向量章节）和 make-list （查阅列表构造章节）

Function: substring string \&rest characters\
&#x20;这个函数返回一个包含字符characters的字符串。

```
(string ?a ?b ?c)
    => "abc"
```

Function: substring string \&optional start end\
&#x20; 这个函数从 string 中抽取某一范围的字符，并将这些字符构造成字符串返回。范围由可选参数 start 和 end 决定。其中字符串的第一位的索引为 0.如果只有一个参数，那么这个函数就会拷贝整个 string。

```
(substring "abcdefg" 0 3)
    => "abc"
```

在上面的例子中， "a" 的索引为 0，"b" 的索引为 1，"c" 的索引为2.而索引3，也就是字符串中的第四个字符的位置，标注了子字符串终止的位置。因此，"abc" 便从原来的 "abcdef" 中拷贝出来。

这个函数同时也支持负的索引，比如 -1 表示字符串的最后一个字符。举个例子：

```
(substring "abcdefg" -3 -1)
    => "ef"
```

在这个例子里，"e" 的索引为 -3，"f" 的索引为 -2，而 "g" 的索引为 -1.因此，"e" 和 "f" 都被包括，但 "g" 被排除在外。

如果 end 为 nil，那么就默认到字符串结尾，因此，

```
(substring "abcdefg" -3 nil)
    => "efg"
```

end 参数缺失和将end设为 nil 是等价的。(substring string 0) 就会返回一整个string的拷贝。

```
(substring "abcdefg" 0)
    => "abcdefg"
```

但是如果是想拷贝整个字符串，我们更推荐使用 copy-sequence（详情查阅 序列函数）。

如果字符串中有的字符具有文本属性，那么文本属性也会被一并拷贝到新的字符串中。详情查阅 文本属性。

函数 substring 同时也可以处理向量。比如：

```
(substring [a b (c) "d"] 1 3)
    => [b (c)]
```

如果 start 不是整数，或者 end 既不是整数，也不是 nil。那么这个函数会抛出 wrong-type-argument 错误。如果 start 标记的位置在 end 之后，或其中有参数超出了字符串范围，那么函数会抛出 args- out-of-range 错误。

另外有一个函数 buffer-substring，用来从当前缓冲区中拷贝子字符串。不过这个函数作用的是缓冲区，而缓冲区的起始索引为 1，而非字符串的 0.

Function: substring-no-properties string \&optional start end\
&#x20; 这个函数和 substing 很相似，但是这个函数并不拷贝文本属性。此外，对于这个函数 start 为 nil，和start 为 0 是等价的。因此，这个函数返回一个无文本属性的字符串。

Function: concat \&rest sequences\
&#x20; 将 sequences 中所有的字符拼接在一起，然后返回（包括文本属性）。其参数必须是字符串、数字列表、数字向量；而参数本身并不会被修改。此外如果 concat 没有收到任何参数，那它就会返回一个空字符串。

```
(concat "abd" "-def")
    => "abc-def"
(concat "abc" (list 120 121) [122])
    => "abcxyz"
;; nil 表示空列表
(concat "abc" nil "-def")
    => "abc-def"
(concat "The" " quick brown " "fox.")
    => "The quick brown fox."
(concat)
    => ""
```

但要记住，这个函数并不总是返回新创建的字符串。调用者需要知道这一点。此外，也不也不要使用 eq 来将其和已存在的字符串进行比较。

因为实际上，返回的字符串可能隐形的改变了参数中的字符串，因此可能会引发潜在的 错误。为了获取绝对安全的字符串，你最好在其返回的字符串上再调用一个 copy-sequence。

你可以在 Mapping函数章节 查看 mapconcat，在 Vector函数章节 查看 vconcat，在 列表构建章节 查看 append。他们其他的拼接函数。对于拼接命令行参数这样的特定工作，请查阅 合并和引用字符串章节。

Function: split-string string \&optional separators omit-nulls trim\
&#x20; 这个函数用来根据分隔符，对string进行分割。其中的分隔符可以为正则表达式（详情查阅正则表达式章节）。所有被正则表达式匹配到的字符都会被当作分隔符；分隔后的子字符串们会以字符串列表的形式返回。

如果参数 separators 为 nil（或缺失），那么默认的分隔符就取决于变量 splite-string-default-separators 。而且参数 omit-null 会默认设置为 t 。

如果参数 omit-nulls 为 nil（或缺失），那么返回的结果将会包含 null 字符，如果该参数为 t，那么 null 字符便会从结果中移除。

如果可选参数 trim 非 nil，那么它必须为正则表达式，而这个正则表达式会对分隔的字符串列表中的每一个子字符串进行匹配。如果匹配结果为空，那么这个子字符串会被当成 null 处理。

如果你将独立的命令行参数分隔开，那么为了配合 call-process 或 start-process 函数，你需要查阅 分隔字符串和解引用章节。

比如:

```
(split-string " two words ")
    => ("two" "words")
```

结果并非 ("" "two" "words" "")。如果你需要这样的结果，那你需要显式地声明分隔符：

```
(split-string " two words "
                split-string-default-separators)
    => ("" "two" "words" "")
```

```
(split-string "Soup is good food" "o")
    => ("S" "up is g" "" "d f" "" "d")
(split-string "Soup is good food" "o" t)
    => ("S" "up is g" "d f" "d")
(split-string "Soup is good food" "o+")
    => ("S" "up is g" "d f" "d")
```

TODO


# 4.4 修改字符串

修改已存在的字符串内容

本章你可以学到修改字符串内容的知识。相关知识查阅可变形章节。

修改已存在字符串内容的最基本的函数是 aset（详情查阅数组函数）。(aset string idx char) 将字符 char 储存到字符串的 idx 位。每个字符可能是单字节的也可能是多字节的。而且如果字符使用的字节数和原字节不同，那么aset会抛出一个错误。

更强大函数有: store-substring:

Function: store-substring string index obj\
&#x20; 这个函数将 obj 储存在 索引 idx 处。参数 obj 既可以是字符，也可以是字符串。

&#x20; 由于字符串的长度是不可变的，当 obj 不符合字符串的实际长度，或任何字符采用了和原字符串不同的编码时，这个函数都会抛出一个错误。

Function: clear-string string\
&#x20; 这个函数将 string  设置为单字节字符串，同时将其内容清零。这个函数可能会改变字符串的长度。


# 4.5 字符串比较

Function: char-equal character1 character2

当两个参数表示同一字符时，该函数返回 t，否则返回 nil。该函数会忽略大小写，除非变量 case-fold-search 的值为 non-nil

```
(char-equal ?x ?x)
     ⇒ t
(let ((case-fold-search nil))
  (char-equal ?x ?X))
     ⇒ nil
```

Function: string= string1 string2

若两个字符串中的字符完全匹配，则返回 t。符号同样可以作为参数传入，此时会使用该符号的名称。该函数对大小写敏感，除非变量 case-fold-search 为 non-nil。

该函数在比较字符串时，和函数 equal 时等价的（查阅相等谓词）。更细一点说，在比较字符串时，会忽略字符串的文本属性。如果你想只比较文本属性，请使用函数 equal-including-propertues。和 equal 不同的地方在于，若参数既不是字符串，也不是符号，则会抛出一个错误。

```
(string= "abc" "abc")
     ⇒ t
(string= "abc" "ABC")
     ⇒ nil
(string= "ab" "ABC")
     ⇒ nil
```

由于技术原因，单字节字符串和多字节字符串当且仅当它们包含的字符序列完全相同，且这些字符的范围在0到127（ASCII）或160到255（八位图）之间。不过需要注意的是，当单字节字符串转换为多字节字符串时，所有在160到255之间的字符都会被转换为对应的，更高的字符码，而ASCII码不会改变。因此，单字节字符串只有在其组成字符全部为ASCII码时，才会和对应转换的多字节字符串 equal。而在160到255之间的字符却不一定一致。因此，包含这一段字符的单字节字符串和多字节字符串的比较可能会出现很多奇怪的现象，不过这种现象很少出现。查阅文本表示章节。

Function: string-equal string1 string2

该函数是函数 string= 的别名。

Function: string-collate-equalp string1 string2 \&optional locale ignore-case

若 string1 和 string2 在 collate 规则下 equal，则该函数返回 t。


# 4.6 字符串转换

本节介绍了字符、字符串和整数之间进行转换的函数。函数 format 和 函数 prin1-to-string 可以将 Lisp 对象转换为字符串。函数 read-from-string 可以将 字符串转换为对应的 Lisp 对象。函数 string-to-multibyte 和 函数 string-to-unibyte 可以改变字符串的文本表示。

\--

函数: number-to-string number

该函数返回数字对应的十进制的字符表示。如果参数为负值，那么返回值会以一个减号开头。

```
(number-to-string 256)
     ⇒ "256"
(number-to-string -23)
     ⇒ "-23"
(number-to-string -23.5)
     ⇒ "-23.5"
```

int-to-string  是该函数的一个别名。

参见 字符串格式化章节的 format 函数。

函数: string-to-number string & optional base

该函数返回字符串对应的数字。若参数 base 要么为 nil，要么为 2-16 之间的一个整数，该参数会作为转换的基数。若参数 base 为 nil，则意味着使用十进制，浮点数转换仅在十进制的时候才会生效；我们没有实现基于其他进制的浮点数转换，因为他们很复杂，而且似乎也没啥用。若一个字符串对应的整数大小超出了 Lisp 整数的范围，那么该函数会将其自动转换为浮点数，并作为结果返回。

字符串解析会跳过字符串起始位置空格、制表符，然后尽可能贪婪的读取可以转换为数字的字符。（在某些系统中，还会忽略其他的空白符号。）如果参数 string 不能被解释为数字，那么该函数会返回 0.

```
(string-to-number "256")
     ⇒ 256
(string-to-number "25 is a perfect square.")
     ⇒ 25
(string-to-number "X256")
     ⇒ 0
(string-to-number "-4.5")
     ⇒ -4.5
(string-to-number "1e5")
     ⇒ 100000.0
```

string-to-int 是该函数的一个别名。

函数: char-to-string character

该函数返回字符对应的字符串。这个函数即将被废弃，因为函数 string 更加通用。查阅 创建字符串章节。

函数: string-to-char string

这个函数返回 string 中出现的第一个字符。该函数和 (aref string 0) 非常相似，不同点在于若 string 为空，该函数会返回 0. (若 string 中第一个字符为 null 字符，ASCII 中的 0 ，则该函数也会返回 0.) 这个函数在将来可能会被废弃，以为它似乎不是很实用。

这里还有一些其他函数用于做字符串转换:

concat

该函数将向量或列表转换为字符串。详情查阅 创建字符串。

vconcat

该函数将字符串转换为向量。详情查阅 向量函数。

append

该函数将字符串转换为列表。详情查阅 构建列表。

byte-to-string

该函数将字符数据中的字节转换为单字节字符串。详情查阅 表示转换。


# 4.7 字符串格式化

格式化指，通过对常量字符串进行替换，从而计算并构造出新字符串的操作。这些常量字符控制如何进行计算，以及如何显示这些字符串；这种操作称为格式化字符串。

格式化字符串通常在调试，记录时非常有用。实际上，函数 message 和 error 同样提供本节介绍的字符串格式化操作；它们和 format-message 的不同点仅仅在于它们如何使用结果。

函数: format string \&rest objects

该函数使用 objects 替换 string 种特定的字符，生成并返回另一个字符串。参数 objects 即是参与计算的值。

string 中除去特定字符外的所有字符都会原样输出，包括它们的文本属性。objects 中的文本属性也会被拷贝并按照原样输出。

生成的字符串很可能不是完全新创建的。举个例子，若 x 是字符串 "foo"，那么表达式 (eq x (format x)) 和 表达式 (eq x (format "%s" x)) 很可能都为 t。

函数: format-message string \&rest objects

该函数和 format 非常相似，不同点在于该函数同样提供重音和撇号的转换。

通常重音和撇号会对应到斜引号，举个例子，"Missing \`%s‘" 会生成 "Missing ’foo‘"。详情查阅 文本引用风格，以了解如何影响这类转换。

通常，格式化标记是一个以 '%' 开头的字符序列。当遇到 '%d' 时，函数 format 会将其替换为 参数 objects 中某个参数对应的打印表示。举个例子：

```
(format "The value of fill-column is %d." fill-column)
     ⇒ "The value of fill-column is 72."
```

format 将字符 '%' 解释为格式化标记，因此该函数的第一个参数需要和后续的参数匹配。这里有一个例子：

```
(format "%s" arbitrary-string)
```

每个特定的格式化标记都要对应着特定类型的数据。如果你提供的值和格式化标记不对应，则会抛出错误。

这里有一个格式化标记集合：

'%s'

使用对象的打印表示替换该标记（使用princ 而非 prin1 输出函数）。中间不会出现 '"'字符，且符号出现的地方也不会有 '\\' 字符。

若对象是字符串，那么还会将文本属性也复制到输出中。'%s'本身的文本属性也会被拷贝，但优先拷贝对象的属性

'%S'

使用对象的打印表示替换该标记（使用 print1 输出函数）。中间会出现引号。因此，字符串会被 '"' 包围起来，且'\\' 会出现在必要的地方。

'%o'

使用对象的八进制表示来替换标记。若传入值为负数，那么输出结果会很依赖具体平台。传入值同样可以为浮点数，在传入后，会将其小数部分略去。

'%d'

使用对象的十进制表示来替换标记。传入值同样可以为浮点数，传入后，会将其小数部分略去。

'%x'

'%X'

使用对象的十六进制来替换标记。传入值为负数时，输出结果会很依赖具体平台。其中 '%x' 使用小写字母，'%X' 使用大写字母。传入值同样可以为浮点数，传入后会将小数部分略去。

'%c'

使用字符替换标记。

'%e'

使用浮点数指数记法来替换标记。

'%f'

使用浮点数小数表示来替换标记。

'%g'

使用指数记法或小数表示来替换标记。（TODO）

'%%'&#x20;

使用 '%' 替代标记。该标记不使用值。例如，(format "%% %d" 30) 返回 "% 30"。

其他任何格式化标记都会抛出 'Invalid format operation' 错误。

这里有几个例子，其中假定使用默认的 text-quoting-style 设置：

```
(format "The octal value of %d is %o,
         and the hex value is %x." 18 18 18)
     ⇒ "The octal value of 18 is 22,
         and the hex value is 12."

(format-message
 "The name of this buffer is ‘%s’." (buffer-name))
     ⇒ "The name of this buffer is ‘strings.texi’."

(format-message
 "The buffer object prints as `%s'." (current-buffer))
     ⇒ "The buffer object prints as ‘strings.texi’."
```


# 自定义格式化


# Case Conversion


# Case Table


# 5 列表


# 5.1 列表与点对


# 5.2 列表相关的谓词


# 5.3 列表元素的存取


# 5.4 列表与点对的构建


# 5.5 列表变量的修改


# 5.6 列表结构的修改


# 5.7 列表和集合的使用


# 5.8 关联列表 Association Lists （alist）

关联列表（Association List）或简称 alist，用于记录键值对的映射。结构上时一个包含若干点对的列表，而这些点对即称为关联（associations）。每个点对的 CAR 部分称为 键（KEY），而 CDR 部分称为 关联值（associated value）。

这里有以一个关联列表的例子。key pine 和 value cones 关联；key oak 和 value acorns 关联；key maple 和 seeds 关联。

```
((pine . cones)
 (oak . acorns)
 (maple . seeds))
```

其中 keys 和 values 可以是 Lisp 中任何对象。比如，在下面的例子中，符号 a 和 数字 1 关联，字符串 “b“ 和 列表 (2 3)关联，

```
((a . 1) ("b" 2 3))
```

有时候，设计一个使用列表而非点对的关联列表来储存信息会更好。比如：

```
((rose red) (lily white) (buttercup yellow))
```

这里，我们认为 rose 作为 key 和 red 关联起来。这种关联列表的一种优点在于，你可以储存其他的相关信息——甚至是一堆其他物品组成的列表——在 CDR 的 CDR 部分中。而一个缺点是，你不可以使用 rassq（见下文）来查找包含指定值的元素。当这优缺点差不多时，这就变成了一种品味问题，不论怎样，你只要保持风格统一就好。

以上的关联列表可以被解释为元素和其CDR部分相关联；比如，key rose 和 列表 (red) 关联起来。

关联列表通常用来记录保留在堆栈里的信息。原因在于，在关联列表中在列表前方可以非常方便的添加关联。当给定一个key，在关联列表中搜寻指定关联时，将会返回第一个被找到的关联（如果有很多关联满足条件的话）。

在 Emacs Lisp 中，关联列表中出现非点对元素并不是个错误。关联列表搜寻函数将会直接忽略掉这样的元素。但很多其他版本的 lisp 可能会在这种情况下抛出异常。

请注意，属性列表和关联列表在很多方面都很相似。属性列表就像一个关联列表，不同的是，属性列表中的 key 仅仅出现一次。详情查阅 属性列表章中 属性列表和关联列表的对比部分。

Function: assoc key alist \&optional testfn\
&#x20; 这个函数返回 alist 中 第一个 key 对应的关联。它会将 key 和 关联列表中的元素的CAR部分使用 testfn 进行比较。默认情况下使用 equal 函数。如果所有大关联都不匹配，则返回nil。比如：

```
(setq trees '((pine . cones) (oak . acorns) (maple . seeds))
    => ((pine . cones) (oak . acorns) (maple . seeds))
(assoc 'oak trees)
    => (oak acrons)
(cdr (assoc 'oak trees))
    => acrons
(assoc 'birch trees)
    => nil
```

这里是另一个例子，不同的是，这里的 key 和 value 并不是符号：

```
(setq needles-per-cluster
        '((2 "Austrian Pine" "Red Pine")
          (3 "Pitch Pine")
          (5 "White Pine")))
        
(cdr (assoc 3 needles-per-cluster))
  => ("Pitch Pine")
(cdr (assoc 2 needles-per-cluster))
  => ("Austrian Pine" "Red Pine")
```

函数 assoc-string 和 assoc 很像，但这个函数忽略了一些字符意义上的不同。详情查阅 文本比较章

Function: rassoc value alist\
&#x20; 这个函数同样返回第一个和 元素的 CDR 和 value 匹配的 关联。如果没有一个关联的 CDR 和 value 匹配（equal），则返回nil。\
&#x20; rassoc  和 assoc 非常相似，不同的地方在于，assoc 取关联数组中每个关联的 car 做匹配，而 rassoc 使用每个关联的 cdr 部分。

Function: assq key alist\
&#x20; 这个函数和 assoc 很相似，不同点在于这个函数使用 eq 做比较。这个函数比 assoc 使用的更加广泛，原因在于 eq 比 equal 更快，而且大多数的关联列表都使用符号作为 key。详情查阅 谓词：相等 部分。

```
(setq trees '((pinr . cones) (oak . acorns) (maple . seeds))
    => ((pine . cones) (oak . acorns) (maple . seeds))
(assq 'pine trees)
    => (pine . cones)
```

因此这个函数不适用于不用符号作为 key 的关联列表中：

```
(setq leaves
      '(("simple leaves" . oak)
        ("compound leaves" . horsechestnut)))
        
 (assq "simple leaves" leaves)
   => Unspecified; might be nil or ("simple leaves" . oak)
 (assoc "simple leaves" leaves)
   => ("simple leaves" . oak)
```

Function: alist-get key alist \&optional default remove testfn\
&#x20; 这个函数和 assq 很相似。它


# 5.9 属性列表 Property Lists （plist）


# 6 序列、数组、以及向量


# 6.1 序列


# 6.2 数组


# 6.3 数组相关函数

本章将会介绍几个操作数组的函数，这些函数可以用在所有的数组类型上。

Function: arrayp object\
&#x20; 当 object 为数组时，该函数返回 t 。（数组包括向量、字符串、布尔向量以及字符表）

```
(arrayp [a])
    => t
(arrayp "asdf")
    => t
(arrayp (syntax-table))    ;; 字符表
    => t
```

Function: aref arr index\
&#x20; 这个函数返回 数组的第 index 个元素。其中，第一个元素对应的索引为0.

```
(setq primes [2 3 5 7 11 13])
    => [2 3 5 7 11 13]
(aref primes 4)
    => 11
(aref "abcdefg" 1)
    => 98         ; "b" 的ASCII码是98
```

类似的函数有 elt，不过是作用在序列上的，查阅序列章节。

Function: aset array index object\
TODO


# 6.4 向量


# 6.5 向量相关函数


# 6.6 字符表




---

[Next Page](/llms-full.txt/1)

