# 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。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://emacs-lisp.ivory.cafe/fu-hao/chuang-jian-bing-interning-fu-hao.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
