> For the complete documentation index, see [llms.txt](https://ayakaleaf-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://ayakaleaf-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 相关的书籍、文章和在线社区中，你会经常看到“tokens”被提及或引用，因此值得简要回顾一下这个主题——你可以在之前发表的一篇文章中找到更多细节 [什么是“TeX 记号”？](/latex/zh-cn/shen-du-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-du-wen-zhang/53-what-is-a-tex-token.md) 在这里，我们将总结关键思想，同时尽量避免过多重复。

让我们先打个比方。假设你正在读一本书，碰到一个不熟悉的词：你会怎么做？今天，几乎肯定是“去搜 Google”，但我们假设你更喜欢一种老办法：你去查一本列出词条并提供其含义的词典。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 将字符序列转换成一个等价整数时所经历的过程，这个整数被称为 **curcs**，并使用该整数在 TeX 的“庞大字典”中查找该命令的含义。查找结果提供两条信息：两个整数，称为 **curcmd** 和 **curchr**，TeX 可以用它们来确切判断该命令做什么，以及随后如何执行它。

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

在内部，TeX 维护着一个名为 **curcs** (**cur**rent **c**ontrol **s**curcs（当前控制序列）的变量，它用于存储 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`。

为处理这一点，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 引擎，一个 *字符记号* 是通过以下公式计算的： $$\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://ayakaleaf-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.
