# 9.3 创建(Creating)并注册(Interning)符号(DONE)

我们先介绍 Lisp 是怎么读取符号的。最重要一个原则，Lisp 确保了 **组成符号名称的字符** 和 **符号本身** 是一一对应的。理解这一点非常重要。

在源码中，当 Lisp 读取器读取到一个符号时，它会首先读取表示该符号名字的所有字符。然后，读取器会在一个称之为 obarray 的表中查找这个名字。这里具体使用的查找技术称为哈希("hashing")，这是一种很高效的查找技术。该技术先将一段字符序列转换为一个数字，也就是我们常说的哈希值("hash code")。举个例子，你想在电话簿中查找Jan Jones的电话号码，相比在电话簿中一页一页的查找，我们先查去 J 索引，再寻找电话号码会更加快捷高效。这就是哈希映射的简单应用。obarray 中的每个元素都是一个“桶”，这些“桶”中储存了给定哈希值的所有符号；先通过哈希值找到相应的 “桶”，然后在这些 “桶”中遍历，寻找目标符号。（在Emacs 中通用哈希表的实现有相同的思路，但是具体的数据类型并不相同，详情查阅 哈希表章。）

如果该符号被找到了，那么读取器便会直接使用这个符号。如果 obarray 中不包含这个符号，那么读取器会创建该符号，然后将其加入到 obarray 对应的“桶”中。寻找并添加符号的过程称为 interning，之后该符号被称为 interned (已注册的) 符号。

interning 保证了同一个 obarray 中的所有符号的名称都是独一无二的。Lisp 中可能会存在同名的符号，但这些同名的符号绝对不可能出现在同一个obarray中，而只能存在于不同的 obarray中。因此，确定obarray的前提下，读取器在读取相同的名字时查找到的符号必定是同一个符号。

通常 interning 会在读取器中自动进行，但有的时候可能会有程序手动 interning。比如，运行M-x命令来从minibuffer中获取某些命令的文本表示后，minibuffer会将该文本作为符号注册。

obarray 不可能储存所有的符号；实际上，有些符号根本就不储存在 obarray 中。这些符号称为 uninterned(未注册) 符号。 uninterned 符号和常规符号相同，都由四个部分构成。不过，获取这些符号的唯一方法是在其他对象中查找或在某些变量的value。uninterned 符号在Lisp的代码生成中十分有用。在你生成的代码中 (通常在写宏的时候)，使用 uninterned 符号，可以保证这些符号不会与 Lisp 程序中使用的其他变量发生命名冲突。

在 Emacs Lisp 中， obarray 实际上是一个向量。该向量中的元素都是一个桶；其中要么装着 hash 到这个桶的 interned 符号，要么装着 0 (表示这个桶内没有符号) 。每一个 interned 符号都有一个内部的指针（对于用户是不可见的）来指向到桶里的下一个符号。因为这些指针是不可见的，使用常规手段是没办法找到所有的符号的。你可以使用后续介绍的 mapatoms 查找一个桶中的所有符号。不过，符号在桶中的具体顺序在绝大多数时候并不重要。

在一个空 obarray 中，每个元素都是0 。你可以使用 (make-vector length 0) 来创建一个 obarray 。**这是唯一的合法创建 obarray 的方法。**&#x901A;常来说，质数长度更有利于哈希操作，长度小于平方根减一效果也不错。

**不要尝试手动将符号放入 obarray 中。**&#x8FD9;并不会起作用——只有 intern 可以正确地将符号放入 obarray 中。

&#x20;**Common Lisp 注意：**&#x548C; Common Lisp 不同，Emacs Lisp 没有提供将一个符号放入放入几个不同 obarray 中的方法。

下面提到的绝大部分函数都接受名称，或 obarray 作为参数。当名名称不是字符串，或 obarray 不是向量时，会抛出 wrong-type-argument 错误。

**Function: symbol-name&#x20;*****symbol***\
&#x20; 这个函数以字符串的形式返回符号的名字。比如：

```
(symbol-name 'foo)
    => "foo"
```

&#x20; 警告：通过替换字符来改变字符串确实改变了符号的名称，但却不会更新obarray，所以不要这样做!

**Function: make-symbol&#x20;*****name***\
&#x20; 这个函数返回一个新创建的，指定名字的 uninterned(未注册) 符号。这个符号的 function 和 value 部分均为空，其属性列表也是 nil 。这下面这个例子中，sym 的 value 和 foo 并不 eq。原因是，sym 是一个 uninterned(未注册) 符号，其名字也是‘foo’。（而符号foo是一个 interned(已注册) 符号）

```
(setq sym (make-symbol "foo"))
    => foo
(eq sym 'foo)
    => nil
```

**Function: gensym&#x20;*****\&optional prefix***\
&#x20; 这个函数返回了一个使用 make-symbol 创建的符号，但名称会使用 gensym-counter 加上前缀 prefix。默认的前缀为 "g"。

**Function: intern&#x20;*****name \&optional obarray***\
&#x20; 这个函数返回一个指定 name 的 interned 的符号。如果 obarray 中没有这个符号，intern 会创建该符号，并将这个符号加入 obarray，然后返回。如果没有提供 obarray ，则会使用默认的全局 obarray。

```
(setq sym (intern "foo"))
    => foo
(eq sym 'foo)
    => t
    
(setq syml (intern "foo" other-obarray))
    => foo
(eq syml 'foo)
    => nil
```

&#x20; **Common Lisp 注意：**&#x5728; Common Lisp 中，你可以将一个已存在的符号 intern(注册) 到一个 obarray 中。在 Emacs Lisp 中，你不能这样做，因为 intern 的参数必须是字符串，而不能是符号。

**Function： intern-soft&#x20;*****name \&optional obarray***\
&#x20; 这个函数返回 obarray 中名字为 name 的符号。如果 obarray 中不存在，则返回 nil。因此，你可以使用 intern-soft 来测试某个符号有没有 interned。如果没有提供 obarray，那么将会使用默认的全局 obarray。

&#x20; 参数 name 可以是符号；在这种情况下，如果 name 已经 interned，函数则会返回指定 obarray 中的 name，否则返回 nil。

```
(intern-soft "frazzle")    ; 不存在 frazzle 符号.
    => nil
(make-symbol "frazzle")    ; 创建一个 uninterned(未注册) 的 frazzle 符号.
    => frazzle
(intern-soft "frazzle")    ; 显然这未注册的符号是不存在于 obarray 中的.
    => nil
(setq sym (intern "frazzle"))   ; 创建一个 interned(已注册) 的 frazzle 符号.
    => frazzle
(intern-soft "frazzle")    ; 在 obarray 中找到了 frazzle 符号!
    => frazzle
(eq sym 'frazzle)          ; 这和 frazzle 是同一个符号 .
    => t
```

**Variable: obarray**\
&#x20; 这个变量是一个默认的全局的 obarray。用来 intern(注册) 和 read(读取) 符号。

**Function: mapatoms function \&optional obarray**\
&#x20; 这个函数对 obarray 中所有的符号调用一次 function。返回值为 nil。如果没有提供 obarray， 则会使用默认的全局变量 obarray，这是标准规定的 obarray。

```
(setq count 0)
    => 0
(defun count-syms (s)
  (setq count (1+ count)))
    => count-syms
(mapatoms 'count-syms)
    => nil
count
    => 1871
```

&#x20; 查阅另一个文档（Accessiong Documentation）的另一个例子。

**Function: unintern&#x20;*****symbol obarray***\
&#x20; 这个函数将 symbol 从 obarray 中删除掉。如果符号并不在 obarray 中，unintern 什么事情都不会做。如果没有提供 obarray，则默认使用全局 obarray。

&#x20; 如果你提供了一个字符串，而不是符号，那默认会将字符串转换成对应的符号。unintern 删除 obarray 中的指定字符串。如果没有这个符号，unintern 将不会做任何事情。

另外，如果unintern 成功删除了指定符号，则返回 t。否则返回 nil。
