基本数据类型之四:数组和序列

Table of Contents

序列是列表和数组的统称,它们的共性是内部的元素都是有序的。elisp 里的数组包括字符串、向量、char-table 和布尔向量。它们的关系可以用下面图表示:

_____________________________________________
|                                             |
|          Sequence                           |
|  ______   ________________________________  |
| |      | |                                | |
| | List | |             Array              | |
| |      | |    ________       ________     | |
| |______| |   |        |     |        |    | |
|          |   | Vector |     | String |    | |
|          |   |________|     |________|    | |
|          |  ____________   _____________  | |
|          | |            | |             | | |
|          | | Char-table | | Bool-vector | | |
|          | |____________| |_____________| | |
|          |________________________________| |
|_____________________________________________|

简单理解:列表是链表、向量是数组

数组的特性:

向量可以看成是一种通用的数组,它的元素可以是任意的对象。字符串是一种特殊的数组,它的元素只能是字符。如果元素是字符时,使用字符串相比向量更好,因为字符串需要的空间更少(只需要向量的1/4),输出更直观,能用文本属性(text property),能使用 emacs 的 IO 操作。但是有时必须使用向量,比如存储按键序列。

char-table 和 bool-vector 使用较少。

测试函数

sequencep 用来测试一个对象是否是一个序列。arrayp 测试对象是否是数组。vectorp、char-table-p 和 bool-vector-p 分别测试对象是否是向量、char-table、bool-vector。

序列的通用函数

一直没有提到一个重要的函数 length,它可以得到序列的长度。但是这个函数只对真列表有效。对于一个点列表和环形列表这个函数就不适用了。点列表会出参数类型不对的错误,而环形列表就更危险,会陷入死循环。如果不确定参数类型,不妨用 safe-length。比如:

(safe-length '(a . b))                  ; => 1
(safe-length '#1=(1 2 . #1#))           ; => 3

取得序列里第 n 个元素可以用 elt 函数。但是建议对于已知类型的序列,还是用对应的函数比较好。也就是说,如果是列表就用 nth,如果是数组就用 aref。这样一方面是省去 elt 内部的判断,另一方面读代码时能很清楚知道序列的类型。

copy-sequence 在前面已经提到了。不过同样 copy-sequence 不能用于点列表和环形列表。对于点列表可以用 copy-tree 函数。环形列表就没有办法复制了。好在这样的数据结构很少用到。

数组操作

创建向量可以用 vector 函数:

(vector 'foo 23 [bar baz] "rats")

当然也可以直接用向量的读入语法创建向量,但是由于数组是自求值的,所以这样得到的向量和原来是一样的,也就是说参数不进行求值,看下面的例子就明白了:

foo                                     ; => (a b)
[foo]                                   ; => [foo]
(vector foo)                            ; => [(a b)]

用 make-vector 可以生成元素相同的向量。

(make-vector 9 'Z)                      ; => [Z Z Z Z Z Z Z Z Z]

fillarray 可以把整个数组用某个元素填充。

(fillarray (make-vector 3 'Z) 5)        ; => [5 5 5]

aref 和 aset 可以用于访问和修改数组的元素。如果使用下标超出数组长度的话,会产生一个错误。所以要先确定数组的长度才能用这两个函数。

vconcat 可以把多个序列用 vconcat 连接成一个向量。但是这个序列必须是真列表。这也是把列表转换成向量的方法。

(vconcat [A B C] "aa" '(foo (6 7)))     ; => [A B C 97 97 foo (6 7)]

把向量转换成列表可以用 append 函数。