> 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/23-how-tex-macros-actually-work-part-5.md).

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

[第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)

## 引言与概述

在第 4 部分，我们回顾了一些 TeX 宏的基本特性，为接下来的两篇文章做准备；在这两篇文章中，我们将深入查看 TeX 宏的底层机制：专门的记号列表。在这最后两篇文章中，我们使用称为节点列表的图示，这些图示来自使用经特殊修改的 Knuth 原版 TeX 软件所生成的数据——这些修改旨在访问通常用户无法接触的 TeX 内部数据结构。通过“接入” TeX 的内部宏处理和执行例程，我们得以写出图形数据，从而能够更详细、更准确地讨论 TeX 的宏处理行为。Overleaf 希望这些图示能帮助读者更好地理解 TeX 宏真正的工作方式。

### 可能的补充背景阅读

Overleaf 已经发布了两篇与记号相关的文章，它们提供了关于 TeX 记号和 TeX 记号列表的补充背景信息。如果你需要填补理解上的任何空白，并帮助自己从本系列的第 5 和第 6 部分中获得最大收获，请务必花时间看看它们。

* [什么是“TeX 记号”？](/latex/zh-cn/shen-ru-wen-zhang/53-what-is-a-tex-token.md)
* [什么是 TeX 记号列表？](/latex/zh-cn/shen-ru-wen-zhang/54-what-is-a-tex-token-list.md)

## 作为记号列表的宏

当 TeX 检测到一个创建宏的命令（`\def`, `\edef`, `\gdef` 或 `\xdef`）在输入流中时，它会触发一个过程，将以下两个部分都转换为 `<参数文本><替换文本>` 将我们宏定义中的内容转换成一个很长的记号列表——但这是一种非常特殊的记号列表。

宏的记号列表与 TeX 中使用的其他记号列表略有不同，因为它们包含“特殊”记号值，这些值只有 TeX 内部的处理过程才能创建/生成：这些特殊记号无法通过你可以在 .tex 文件中包含的任何命令直接创建。TeX 创建并使用这些“特殊”记号值来帮助处理你的宏调用，下面我们将探讨并解释这一点。

### 简要说明记号列表如何存储：节点

为了存储一个记号（整数值）列表，TeX 使用一种称为……的数据结构 [链表](https://en.wikipedia.org/wiki/Linked_list)，在 TeX 中，它由一系列所谓的 *节点*。你可以把节点看作一个可用于存储一组数据项的小块计算机内存。为了存储一个宏，这些节点像链条一样串联起来，其中每个节点（链中的一环）可以存储若干信息——包括一个记号值以及列表中下一个节点的内存地址。更多信息可阅读文章 [什么是 TeX 记号列表](/latex/zh-cn/shen-ru-wen-zhang/54-what-is-a-tex-token-list.md) ，但下图总结了作为记号列表存储的宏的关键特征：

![作为链式节点列表存储的 TeX 宏记号列表示意图](/files/98697fc4664046a2e3e86e3bd77c314df993a36e)

### 提醒：宏定义的 4 个部分

如第 4 部分所述，任何宏的结构都可写为：

```
<TeX 宏原语><宏名><参数文本>{<替换文本>}
```

其中：

* `<TeX 宏原语>` = 其中之一 `\def`, `\edef`, `\gdef` 或 `\xdef`;
* `<宏名>`= 你的宏的名称，例如 `\\foo`;
* `<参数文本>` 可以为“空”（不存在），也可以是一串分隔符记号和宏参数记号；
* `<替换文本>` 是宏的实际主体：即在调用宏时“执行”的部分。

**注意**：（如第 4 部分也指出的）在整个讨论中，我们假设 `<宏名>` 后面会跟一个类别码为 10 的空格字符，作为终止……的分隔符 `<宏名>`。 *没有* 在文本/讨论中明确显示该空格字符，但我们假定它存在。严格来说，我们应当像这样表示它：

```
<TeX 宏原语><宏名><空格><参数文本>{<替换文本>}
```

不过，我们会省略显式包含一个 `<空格>` 字符，并默认假设它存在。

**注意**：这些字符 `{` 和 `}` *不会* 成为宏记号列表的一部分：它们的作用只是告诉 TeX 的输入扫描器（它负责创建记号）在何处 `<替换文本>` 开始和停止。

当 TeX 定义一个宏时，这些部分 `<参数文本><替换文本>` 会被转换成一个连续很长的记号列表——该列表中的记号总数取决于宏的复杂度。正如我们所见，该部分的特定用途是充当 TeX 用来选取构成参数（值）的记号的“记号模板”或“蓝图”；也就是说，用来喂入实际宏的记号： `<替换文本>`.

为了巩固这些概念，我们来举一个宏的例子，但保持它简短一些，以免后续图示过于杂乱：

```
\def\foo A#1\fake{123 #1}
```

对于我们的宏， `\\foo`

* `<参数文本>` = `A#1\fake`
* `<替换文本>` = `123 #1`

虽然这个例子是一个简单的宏，但它包含了我们需要探讨的所有特性。

如前所述，TeX 会将 `<参数文本><替换文本>` 转换成一个很长的记号列表，你可以在下图中看到。在我们的例子中，由 `A#1\fake{123 #1}` 形成的记号已被转换为存储在记号列表中的连续记号序列（作为节点链表）。

## 展示真实宏记号列表的图示

下图展示了宏如何 `\def\foo A#1\fake{123 #1}` 被存储，使用的是来自 TeX 引擎内部的真实数据。它是用 [经定制的 Knuth TeX 版本](/latex/zh-cn/shen-ru-wen-zhang/01-a-new-series-of-articles-tex-tokens-and-related-concepts-but-why-and-how.md#how-can-you-study-tex-tokens3f) 进行了修改，加入了额外代码以拦截宏调用、检查 TeX 的内部数据，并将其导出为供一个名为 [Graphviz](https://www.graphviz.org/).

你可以将下图下载为 [PDF 文件](https://assets.ctfassets.net/nrgyaltdicpt/N6gT757eXCxRV3FtdPPga/7ce120dc05ed05962bb911ff1124734b/annotatednodelist-plain.pdf) （675 KB）或 [SVG 文件](https://images.ctfassets.net/nrgyaltdicpt/4PkmtHDhO8KF892ZDWuLHP/2c9c8385c6948fd122b228c5c780a3a6/annotatednodelist-plain.svg) （1.8 MB）。

![带注释的 TeX 记号列表示意图](/files/f232b32daf81cb3d5bab28448aaf7728d4cc3f41)

#### 理解这些节点

在上图中你会看到，每个节点都包含两个称为 **下一个节点** 和 **当前节点**的数据项。这些只是表示 TeX 内存位置的整数值——其他节点存储在这些位置。 **下一个节点** 和 **当前节点** 这些值并不重要，它们只是存储这些位置（内存地址），使节点能够链接成一个列表。

![下一个节点和当前节点的含义](/files/314b78934c31d65069f971aa16e56d88e676b73e)

#### 回到示例

在节点图中，由 `A#1\fake{123 #1}` 形成的记号列表包含本文开头引入的几个“特殊记号”。此外，表示我们宏的节点列表以一个“特殊的首节点”开头：我们将探讨它们是什么以及它们的作用。

宏记号列表（以及某些其他类型的记号列表）中的第一个项目并不存储记号值，而是存储一个称为该宏的 *引用计数* ，TeX 用它来跟踪宏的使用情况。

![引用计数节点是记号列表中的第一个节点](/files/a7e27d95f562d101600bac48e408b1580e6e5873)

……的第一个记号 `<参数文本>` 存储在紧跟引用计数之后的节点中：你可以看到它是一个表示字母 `一个` ，其类别码为 11。根据第 2 和第 3 部分的讨论，我们知道字符记号是使用……计算出来的

$$\text{token value}=256\times \text{category code} + \text{character code}$$

其中，对于字母 `一个` 且类别码为 11 时，为

$$\text{token value}=256\times 11 + 65$$

其值为 2881，如节点中所示。

#### 在 \foo 中使用的“命令” \fake

在我们的宏定义中 `\def\foo A#1\fake{123 #1}` 其中一个分隔符是一个 *未定义的* 命令 `\fake` 它被存储在记号列表中，作为……的一部分 `<参数文本>` 部分。你可以看到，在整个宏记号列表中 `\fake` 有一个记号，其值为 `19491`——这是 TeX 使用第 3 部分讨论的公式计算出的整数值。当 TeX 尝试执行 `\\foo` 它会期望找到 `\fake` 记号值，位于……的末尾 `<参数文本>` 部分。TeX *不会* 尝试执行该 `\fake` 命令，因为它的作用仅仅是在……中提供一种“标点” `<参数文本>` “记号模板”。

![将命令记号用作宏分隔符](/files/6ce38134665697942a7b7874ee9751fb346e6b1b)

#### 记号列表中的特殊记号

**“匹配结束”记号**

在调用宏时，TeX 的第一项任务是扫描用户键入的宏，并比较用户的……中存在的记号 `<参数文本>` 部分与模板中包含的记号 `<参数文本>` 存储在内存中（在定义宏时创建）。由于宏的完整定义，由……构成 `<参数文本><替换文本>` 被存储为一个很长的连续记号列表，TeX 需要知道在该记号列表中哪里 `<参数文本>` *停止* 以及哪里 `<替换文本>` *开始*。为此，当 TeX 定义宏（构建记号列表）时，它会插入一个称为……的特殊终止记号 **匹配结束** 记号，作为由……生成的一组记号中的最后一个记号 `<参数文本>`。 **匹配结束** 记号不能由用户命令生成，只有 TeX 自身才能创建它，因此 TeX 可以确定检测到……的结束 `<参数文本>`.

![在 TeX 记号列表中显示“匹配结束”记号](/files/d50ec86132bc285de25726c3749f00c11bed7453)

在这里，我们可以看到紧随……之后的第一个记号 **匹配结束** 是一个表示数字……的记号 `1` ，其类别码为 12。这是意料之中的，因为 `<替换文本>` 对于我们的宏 `\\foo` 是 `123 #1`——也就是说，它以表示数字……的记号开头 `1` （类别码为 12）。

根据第 2 和第 3 部分的讨论，我们知道字符记号是使用……计算的

$$\text{token value}=256\times \text{category code} + \text{character code}$$

其中，对于数字 `1` 且类别码为 12 时，为

$$\text{token value}=256\times 12 + 49$$

其记号值为 3121，如节点中所示。

**“匹配参数”记号**

当 TeX 存储宏定义时，它会将任何参数记号（`#1`, `#2`… `#9`）中的……转换为 `<参数文本>` 一种称为 **匹配参数** 记号。这些记号告诉 TeX，它需要开始在用户的宏调用中寻找作为宏参数的记号。

![在 TeX 记号列表中显示“匹配参数”记号](/files/fd4885f2380ecf467e4054b34330441514003060)

### 记号列表中的特殊记号

#### “输出参数”记号

当 TeX 处理完所有内容并准备实际运行（展开）宏时， **输出参数** 记号会指示 TeX 在……中的位置 `<替换文本>` 在那里它需要填入表示用户在调用宏时提供的参数的记号。实际上就是：“在这个位置插入表示用户第 n 个参数的记号，其中 n=1...9”。

在……中 `<替换文本>` 存储的宏定义记号列表的……部分中会有一个 **输出参数** 对应于每一个……的记号 `#1`, `#2`... `#9` 原始定义中出现的。

![在 TeX 记号列表中显示“输出参数”记号](/files/d2ad8ab0de298c926b1eb5991501183d46203536)

如果我们看一下我们对……的定义 `\\foo` (`\def\foo A#1\fake{123 #1}`）我们会看到只有 1 个宏参数（`#1`在 `<参数文本>` (`A#1\fake`）因此只有 1 个宏参数（`#1`）出现在 `<替换文本>` (`123 #1`）：这就导致只有 1 个 **输出参数** 记号出现在表示……的记号列表中 `<替换文本>`.

请注意表示……的节点列表中的以下内容 `\\foo`的 `<替换文本>`:

* 紧挨着……之前的那个记号 **输出参数** 记号表示一个空格字符（类别码 10，字符码 32），因为在……和 `123` 以及宏参数（`#1`）在……的原始定义中 `\\foo`;
* 该 **输出参数** 是列表中的最后一个记号：该 **下一个节点** 具有“null”（意为空）的特殊值，用于终止列表：在……之后不再有节点 **输出参数** 因为它是最后一个记号，表明……的结束 `<替换文本>` 也就是宏定义的结束。

## 第6部分

在第 6 部分，我们将使用一些详细图示来解释并探讨宏展开的确切含义，以及 TeX 在将宏参数喂入宏的……之前对其进行记号化所带来的后果 `<替换文本>`.

[第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/23-how-tex-macros-actually-work-part-5.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.
