> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-cn/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md).

# TeX 宏究竟是如何工作的：第3部分

[第1部分](/latex/zh-cn/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.md) [第2部分](/latex/zh-cn/geng-duo-zhu-ti/20-how-tex-macros-actually-work-part-2.md) [第3部分](/latex/zh-cn/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md) [第4部分](/latex/zh-cn/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md) [第5部分](/latex/zh-cn/geng-duo-zhu-ti/23-how-tex-macros-actually-work-part-5.md) [第6部分](/latex/zh-cn/geng-duo-zhu-ti/24-how-tex-macros-actually-work-part-6.md)

## 该暂停了！

在继续这个故事的下一部分之前，我们需要先回顾一下：提醒自己我们要去向何方，并整理思路，确保所有关键思想都已就位。提醒一下，我们的示例基于这样一个假设：TeX 已经读入了一行包含 `Hello World \jobname` 并且 TeX 正在对其排版，以构建一个段落。

### 最终目标

我们的目标是更好地（更深入地）理解 TeX 宏的本质以及它们如何工作。然而，要做到这一点，我们首先需要理解 TeX 如何读取输入文件并处理其中的字符。下面是迄今为止所涵盖主题的摘要。

* TeX 会读取（扫描）你输入中的每一个字符，并且对于每个字符，TeX 都有两条信息：
* **字符代码**：用于标识该字符的整数，例如，当它存储在 .tex 输入文件中时；
* **类别码**：另一个整数，属于 TeX 内部，用于为从输入中读入的每个字符赋予含义。
* 一旦某个字符被 TeX 读入，每个字符的类别码就会 *永久* 通过创建一个字符记号与该字符单独关联：
* TeX 使用一个简单的公式，将字符代码及其对应的类别码“打包”成一个称为字符记号的整数。
* 你可以改变 TeX 已经 *尚未读入* 的任何字符的含义，只需为你希望改变其行为的字符分配一个不同的类别码——也就是说，修改 TeX 处理该字符的方式。
* 重新定义（重新映射）类别码是通过 TeX 的原语 `\catcode` 命令。
* 来实现的。当 TeX 看到类别码 0 时，它会切换到一种特殊的扫描模式，并开始寻找一个命令：要么是一个（可能由多个字母组成的） *控制词* ，要么是一个单字符 *控制符号*.

到目前为止，我们已经把 TeX 的输入扫描过程看作是它识别单个字符并使用每个字符的类别码来判断下一步该做什么。一些字符只是用于排版的普通文本项（例如，类别码 11），但我们也看到了空格字符（类别码 10）和转义字符（类别码 0）。还有其他一些类别码，为了简洁起见，我们还没有讨论，例如类别码 1（“开始组”，例如， `{`）类别码 2（“结束组”，例如， `}`）以及其他类别码。每一种类别码在 TeX 的输入扫描以及随后由 TeX 内部的软件过程/算法进行的处理 中都扮演着各自的角色。

### 记号：快速回顾

“记号”这一概念是 TeX 工作方式的核心：你会在 TeX 相关的书籍、文章和在线社区中不断看到“记号”一词或相关引用，因此值得简要回顾一下这个主题——你可以在一篇此前发表的文章中找到更多细节 [什么是“TeX 记号”？](/latex/zh-cn/shen-ru-wen-zhang/53-what-is-a-tex-token.md)

我们已经看到，TeX 通过将字符代码和类别码合并为一个单一的复合整数，把输入字符转换为记号。TeX 对命令也做类似的事情：使用命令的名称，它计算出一个称为 *命令记号* 的整数（我们稍后会更详细地探讨这一点）。作为指导，你可以把记号看作是 TeX 用来“打包”它从输入中读入的项目的方式，使它们准备好被发送到 TeX 处理的下一阶段。将所有项目（字符或命令）整齐地封装为一个数字表示，使得在处理链的后续阶段更容易进行处理。例如，当 TeX 想要存储你的一些输入以便以后使用，比如宏定义时，TeX 只需要把你的宏定义——无论多么复杂——保存为一系列整数，其中每个整数都是一个记号，表示构成（包含于）你的宏定义的一部分的一个字符或一个命令。

## 那么，接下来呢？

在第 2 部分的最后一节中，我们看到转义字符（类别码 0）如何将 TeX 切换到一种特殊的处理模式，在这种模式下它会寻找一个命令的名称。在我们的示例中，TeX 检测到了字符串 `jobname` ，我们在第 2 部分结束时，TeX 正准备对这串字符（命令名）“做点什么”。在本部分中，我们将详细看看 TeX 接下来做了什么。

一旦 TeX 识别出你输入文件中的某一串字符代表某个命令的名称（这里是 `jobname`）TeX 可能会，根据它当时正在做的事情，需要执行该命令。我们说“可能需要”是因为有些时候 TeX 不会立即尝试执行一个命令：例如，当它正在定义一个宏时（TeX 正在构建记号列表）——这些主题我们稍后会讨论。不过，我们将继续沿用我们的示例，其中 TeX 正在排版一个段落，并且 *会*，在这种情况下，需要执行 `\\jobname`.

### 从一串字符到执行命令：如何实现？

首先，让我们回顾一下第 2 部分中的图 5b，其中 TeX 已经识别出输入中的某一串特定字符构成了一个命令的名称： `jobname`。图 5b 表明 TeX 必须“检查内部表……”。这 *究竟是什么意思*?

![TeX 在寻找命令名](/files/e4659b28300bfb769e171e68f27dd2d181a7aff6)

关于 TeX 如何“检查内部表”以便从一串字符（例如， `jobname`）过渡到准确确定该命令是什么以及它的含义，更为详细的说明可以在一篇之前的文章中找到 [什么是“TeX 记号”？](/latex/zh-cn/shen-ru-wen-zhang/53-what-is-a-tex-token.md) 在这里，我们会在尽量避免过多重复的同时总结关键思想。

让我们从一个类比开始。假设你正在读一本书，遇到一个不熟悉的词：你会怎么做？如今，几乎肯定是“去谷歌一下”，但让我们假设你更喜欢更老派的方法：你去拿一本列出词语并给出其含义的字典。TeX 有一个类似的机制：一个内部“词典”，其中列出了当前 TeX 已知的所有命令以及这些命令的“含义”。这里所说的“含义”指的是：这是什么类型的命令、它做什么，以及 TeX 运行该命令可能需要的任何其他信息。还要注意，“命令”这一术语包括用户/TeX 程序员编写的任何 TeX/LaTeX 宏，以及 [数百个内建原语命令](/latex/zh-cn/geng-duo-zhu-ti/46-tex-primitives-listed-by-tex-engine.md).

继续我们的字典类比。当我们作为人类读者，需要查找一个词的含义时，我们会使用字典提供的按字母顺序排列的词条来搜索字典——但当然，TeX 的工作方式并不完全如此。回到我们最初的 `jobname` 示例，TeX 如何在它的“字典”中找到 `jobname`的“含义”——而这个“含义”到底向 TeX 提供了什么？

TeX 并不是为它所知道的所有命令提供一个内部“按字母顺序排列的列表”，它做的是稍有不同的事情。TeX 将命令名称中出现的整串字符转换为一个单独的整数，这个整数将用于标识（表示）该命令。TeX 在内部维护着一个包含所有已知命令的大型“字典”，它把从命令名计算出来的 *整数* ——注意，这个字典并不把实际的命令名称本身当作字母序列（称为 *字符串*）来存储。TeX 对其所有内建命令（原语）都使用这个字典， *和* 它也会用它来存储用户创建的任何宏（命令）的详细信息：你的宏的名称会被转换成一个整数，而这个整数会在 TeX 的字典中被“注册”起来。

每当 TeX 检测到你输入中使用了一个命令，并且需要知道该命令的一些信息时，它就会把命令名中的那串字符转换成一个“等价”的整数，并用这个整数在它的“大字典”中查找该命令。你们中的程序员可能会想知道，TeX 使用一种 [哈希函数](https://en.wikipedia.org/wiki/Hash_function) 来完成这种转换。

![哈希函数示意图](/files/813f72c54621f9e1fc7d333e31b43dc646f59db2)

### 图 6：从字符到命令含义

下面的图展示了一个命令在 TeX 中的旅程：TeX 将那串字符转换成一个等价的整数，这个整数被称为 **curcs**，并用这个整数在 TeX 的“大字典”中查找该命令的含义。该查找的结果包含两条信息：两个整数，称为 **curcmd** 和 **curchr**，TeX 可以利用它们准确地判断命令做什么以及随后如何执行它。

![TeX 将一串字符转换成一个等价的整数，以查找该命令的含义](/files/89106a98b77b7615e206d5af3ba4941f165de249)

在内部，TeX 维护着一个名为 **curcs** (**cur**rent **c**ontrol **制**equence) 的变量，用于存储 TeX 当前正在处理的命令的整数值——也就是， **curcs** 存储根据命令名计算出来的整数。这还不是全部，因为还有一个细节：如果 TeX 刚刚读入/处理的是一个字符，而不是一个命令，它会把 **curcs** 设为 0，用来记住刚才读入的是字符，而不是命令。

### 命令对 TeX 来说意味着什么

如果我们查看 TeX 引擎提供的 [内建命令集合](/latex/zh-cn/geng-duo-zhu-ti/46-tex-primitives-listed-by-tex-engine.md) ，我们会发现其中有些命令彼此密切相关：它们执行相似的任务；例如，所有 TeX 引擎都使用 4 个原语命令来定义（创建）宏： `\def`, `\gdef`, `\edef`, `\xdef`。这 4 个命令都用于定义宏，但当然，每个命令的实现方式略有不同。如果从编程的角度来看：这里有 4 个宏定义命令，它们大体上做的是同一件事，但我们需要在它们之间进行选择，以适应各自的行为。

为了解决这个问题，TeX 为每个命令分配两个值，而这两个值就是 TeX 所理解的命令“含义”（它的角色/它做什么）——这两个值属于 TeX 内部，深藏在软件之中，是用户无法访问的“内部机制”的一部分。每一个 TeX 命令，无论是内建原语还是用户定义命令，都会被分配两个值，对 TeX 来说，这两个值定义/分类了它的行为——也就是它对 TeX 的含义。当 TeX 使用它的“大字典”查找一个命令时，它会找到这两个至关重要的信息：

* **命令码**：一种“通用分类”，表明它属于什么“类型”的命令——例如，“宏定义”命令（ `\def`, `\gdef`, `\edef`, `\xdef`中的一种）；“造盒”命令（ `\hbox`, `\vbox` 或 `\vcenter`中的一种），以及 TeX 引擎支持的数百个命令中的其他类型。用户定义的宏也会被分配一个命令代码。
* **命令修饰符**：这是提供给 TeX 的附加信息，用于为某个命令提供具体细节。用户定义的宏也会被分配一个命令修饰符——不过，对于宏来说，命令修饰符所扮演的角色与原语略有不同（对于宏而言，命令修饰符指示宏定义存储在内存中的位置）。

把命令代码和命令修饰符放在一起，就能唯一标识每个命令。下面是 Knuth 最初的 TeX 软件中用于宏定义命令的命令代码和命令修饰符——注意，其他 TeX 引擎可能使用不同的值，但它们遵循完全相同的原理：

|         |                                                   |                                                    |
| ------- | ------------------------------------------------- | -------------------------------------------------- |
| **命令**  | <p><strong>命令</strong><br><strong>代码</strong></p> | <p><strong>命令</strong><br><strong>修饰符</strong></p> |
| `\def`  | 97                                                | 0                                                  |
| `\gdef` | 97                                                | 1                                                  |
| `\edef` | 97                                                | 2                                                  |
| `\xdef` | 97                                                | 3                                                  |

### 回顾：理解所有这些变量/值

此时我们已经被大量关于值、变量、命令值以及各种细节的信息淹没了——这很容易让人困惑，所以让我们梳理一下我们知道的内容。当 TeX 从你的输入中读取某些内容时，它要么是字符，要么是命令。每当 TeX 从输入中读取某些东西时，它都需要存储刚刚读入（扫描）内容的信息：

* 对于 **字符**：它需要记录字符代码和类别码。它还需要创建并存储 TeX 根据这些值计算出的记号值。
* 对于 **命令插入边注**：TeX 需要知道它根据命令名计算出的数值等价物， **curcs**。它还可能需要存储通过在 TeX 的“字典”中查找该命令而检索到的“含义”：命令代码和命令修饰符。除此之外，TeX 还需要计算一个代表该命令的记号值。

是的，这很令人困惑：有很多变量和记号概念在四处飘来飘去，所以让我们试着理清它。

在内部，TeX 使用四个全局变量来存储 TeX 最近读入（或当前正在“处理”）项目的信息——我们不会详细讨论这些变量，但知道它们的存在有助于提供一些背景，以理解真正发生了什么：

* **curcmd**： （当前命令）一个整数变量。它用于存储 *当前命令* 值，即当前正在处理的命令的值 *或* 它存储当前 *类别码* 正在处理的字符的；
* **curchr**： （当前字符）一个整数变量，但它存储什么取决于 TeX 刚刚从输入中读取了什么：
* **字符**：如果最近读入的项目是一个字符， **curchr** 存储当前 *字符代码*.
* **命令**：如果最近读入的项目是一个命令， **curchr** 存储 *命令修饰符*：TeX 用于支持/明确 **curcmd**的附加信息——因为，正如我们上面所见，有些命令共享相同的 **curcmd**
* **curcs**： （当前控制序列）一个整数变量，存储从命令名中的字符序列计算出的值。 **curcs** = 0，如果最近读入的是单个字符，而不是控制序列（命令名）；
* **curtok**： （当前记号）一个整数变量，用于保存当前记号的值——它要么是命令记号，要么是字符记号。

以下是以上信息以表格形式展示：

|                    |                                                                                                                                                                             |                                                                          |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **TeX 内部使用的全局变量：** | **当 TeX 扫描一个字符时：**                                                                                                                                                          | **当 TeX 扫描一个命令时：**                                                       |
| **curcmd**         | 存储当前字符的类别码                                                                                                                                                                  | 存储 *命令码*——它标识当前命令的“类型”                                                   |
| **curchr**         | 存储当前字符的字符代码                                                                                                                                                                 | 存储补充数据（称为 *命令修饰符*），为当前命令提供附加信息                                           |
| **curcs**          | 0                                                                                                                                                                           | 一个非零正整数，通过对命令名中的字符串使用哈希函数计算得出。它用于访问 TeX 的“字典”，以查找当前命令的含义——获取其命令代码和命令修饰符。 |
| **curtok**         | 对于 8 位 TeX 引擎，一个 *。通过计算这个会传递给 TeX 内部处理的记号值，该特定* 是按以下公式计算的： $$\text{curtok}=256\times \text{curcmd} + \text{curchr}$$ 其中 $$\text{curcmd}$$ 是字符的类别码，而 $$\text{curchr}$$ 是字符代码 | 对于 8 位 TeX 引擎，一个 *命令记号* 是按以下公式计算的： $$\text{curtok}=4095 + \text{curcs}$$ |

### 关于当前记号的进一步说明

对于字符而言，最大可能的记号值是使用最大的类别码（15）和最大的字符代码获得的；对于 8 位 TeX 引擎，这个字符代码是 255。理论上（对于 8 位 TeX 引擎而言），最大字符记号值 $$\text{curtok}\_{\text{max}}$$是：

$$\text{curtok}\_{\text{max}}= 256\times 15 + 255 = 4095$$

我们说“理论上”，是因为类别码 15 用来表示“无效字符”，这会导致 TeX 产生错误：无效字符永远无法通过 TeX 的输入扫描过程，因此它永远不会成为一个字符记号。

对于命令，当前记号（$$\text{curtok}$$）是从 $$\text{curtok}=4095 + \text{curcs}$$ 计算出来的，但对于命令 $$\text{curcs}$$ 是 *总是* 非零，因此 TeX 可以轻松判断一个记号代表什么：

* 如果 $$\text{curtok} > 4095$$ 那么它就是一个命令记号；
* 如果 $$\text{curtok} < 4095$$ 它就是一个字符记号。

实际上，TeX 使用记号——一种简单的整数值——来“打包”它需要知道的关于从输入中读入的项目的全部信息。

## 第4部分

在第 4 部分中，我们将通过一系列示例宏来演示一个宏的 `<参数文本>` 部分充当“记号模板”，可通过使用分隔符记号来构造。

[第1部分](/latex/zh-cn/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.md) [第2部分](/latex/zh-cn/geng-duo-zhu-ti/20-how-tex-macros-actually-work-part-2.md) [第3部分](/latex/zh-cn/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md) [第4部分](/latex/zh-cn/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md) [第5部分](/latex/zh-cn/geng-duo-zhu-ti/23-how-tex-macros-actually-work-part-5.md) [第6部分](/latex/zh-cn/geng-duo-zhu-ti/24-how-tex-macros-actually-work-part-6.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-cn/geng-duo-zhu-ti/21-how-tex-macros-actually-work-part-3.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
