> 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/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md).

# \expandafter 是如何工作的：从基本原理到探索 TeX 源代码

[第1部分](/latex/zh-cn/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md) [第2部分](/latex/zh-cn/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md) [第3部分](/latex/zh-cn/shen-ru-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) [第4部分](/latex/zh-cn/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md) [第5部分](/latex/zh-cn/shen-ru-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md) [第6部分](/latex/zh-cn/shen-ru-wen-zhang/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.md)

## 引言

我们现在已经涵盖了完整探讨所需的背景主题 `\\expandafter`:

* TeX 记号的基础以及它们是如何计算的；
* TeX 展开过程背后的原理；
* TeX 在文档处理过程中对临时记号列表的使用/创建；
* TeX 如何使用并“周旋于”多个输入源（包括临时记号列表）。

在本文中，我们将把这些主题/概念结合起来，解释 TeX 的……所依据的机制 `\\expandafter` 命令：简而言之，就是它如何工作。

## 那么，关于 \\\expandafter

背后的思想 `\\expandafter` 就是在 TeX 通常展开某个命令（记号）之前，强制对其进行展开。给定两个记号， $$\mathrm{T\_1}$$ 和 $$\mathrm{T\_2}$$ ……的作用 `\\expandafter` $$\mathrm{T\_1T\_2}$$ 会导致 TeX 处理 $$\mathrm{T\_1}\text{<}$$对……的展开 $$\mathrm{T\_2}\text{>}$$，其中 $$\text{<}\dots\text{>}$$ 表示一个记号列表。TeX 会展开 $$\mathrm{T\_2}$$ 提前进行，以便记号 $$\mathrm{T\_1}$$ （例如，原语或宏）能够看到，或能够作用于，由……展开而产生的记号 $$\mathrm{T\_2}$$。如果记号 $$\mathrm{T\_2}$$ 表示一个不可展开项，例如非活动字符或（大多数）原语，那么……的作用 `\\expandafter` 不会改变任何东西：TeX 仍会继续处理记号 $$\mathrm{T\_1T\_2}$$ ，按正常方式。

### 使用 \\\expandafter 的介绍

如果你还没用过 `\\expandafter`，下面是一个将它与原语 `\\uppercase{...}`一起使用的例子。假设我们想把我们的主 `.tex` 输入文件的名字排版出来，但使用大写字母。我们可能知道以下 TeX 原语命令：

* `\\uppercase`：顾名思义，会将字符记号转换为其对应的大写形式（如果存在的话）；
* `\\jobname`：如我们所见，会展开并给出主……的名称 `.tex` 文件。

假设我们的 TeX 文件名为 `mycode.tex` 我们本来合理地会期望 `\\uppercase{\\jobname}` 排版出 `MYCODE`。但并不是，它排版出来的是 `mycode` ，仍是小写。哪里“出了错”呢？

如果我们把 `\\uppercase` 的通常用法写成

```
\\uppercase{<token list>}
```

我们可以说 `\\uppercase` 会浏览 `<token list>` 并且只会对……进行操作（改变大小写） *字符记号* 它在……中检测到的 `<token list>`：所有非字符记号都会被 *忽略* 因为 `\\uppercase` 不会“查看内部”（展开）非字符记号来看看它们包含或表示什么。因为一个记号本质上只是一个整数值，所以 `\\uppercase` 所要做的只是浏览记号列表，检查其中每个记号的数值是否在 `<token list>` 落在表示字符记号的数值范围内。顺带一提， `\\uppercase` 也会 *改变活动字符的大小写* 以创建一个大写活动字符；由于它仍然是活动的，它也必须已经被定义，否则 TeX 会产生错误： `未定义的控制序列`，不过我们扯远了……

例如，即使我们定义了一个只有文本的宏

```
\\def\\foo{some lower-case text}
```

那么 `\\uppercase{\\foo}` 仍然排版出 `some lower-case text` 而不是 `SOME LOWER-CASE TEXT` ，并不像我们希望的那样，仅仅因为……的作用 `\\uppercase` 不会试图确定 `\\foo` 表示什么：它把 `\\foo` 看作一个命令记号并忽略它，就像它对……所做的那样 `\\jobname`.

### 我们该如何修复这个问题？\\\expandafter 来救场了

要将文件名排版成大写版本，我们需要修改 `\\uppercase{\\jobname}` 通过强制 TeX 用……替换 `\\jobname` 及其展开结果（一个字符记号序列） *在……之前* `\\uppercase` 开始工作。再次，展开被用于移除 `\\jobname` 记号（命令），并将其替换为它的展开结果（一个包含字符记号的记号列表）。因此，如果我们写

```
\\uppercase\\expandafter{\\jobname}
```

那么它就能工作： `MYCODE` 会被排版出来。发生的情况是 TeX 开始处理 `\\uppercase` 并立即检查必需的左花括号字符（`{`）；然而，TeX 检测到一个 `\\expandafter` 命令，这使它暂时“转移注意力”去处理 `\\expandafter{\\jobname}`.

如果我们比较

`\\expandafter` $$\mathrm{T\_1T\_2}$$

与我们的例子

`\\expandafter{\\jobname}`

我们可以看到

* $$\mathrm{T\_1} =\space$$`{`<sub>记号</sub>
* $$\mathrm{T\_2} =\space$$`\\jobname`<sub>记号</sub>

其中 `{`<sub>记号</sub> 和 `\\jobname`<sub>记号</sub> 指 TeX 计算出的记号值——下标记法 <sub>记号</sub> 用于提醒我们 TeX 是在整数记号的世界中工作的。

写成 `\\uppercase\\expandafter{\\jobname}` 之所以可行，是因为，概括地说（细节随后说明）， `\\expandafter` 会促使 TeX 执行以下任务：

1. 读取并保存开头的 `{`<sub>记号</sub>;
2. 读取下一个记号： `\\jobname`<sub>记号</sub>。TeX 识别出 `\\jobname`<sub>记号</sub> 表示一个可展开的命令，并对其进行展开。 `\\jobname`<sub>记号</sub> 被其展开结果替换——一系列字符记号；
3. 在展开了 `\\jobname` 命令之后，TeX 将 `{`$$\_\mathrm{token}$$ “放回输入中”，并使用由……展开产生的记号列表 `\\jobname` 这样 TeX 就会读取 `\\uppercase{`<sub>记号</sub>`<\jobname 的展开结果>`<sub>记号列表（字符）</sub>`}`，从而得到我们想要的结果。

下图展示了 TeX 如何处理 `\\uppercase\\expandafter{\\jobname}`——请从图的底部向上阅读，以跟随处理流程。

![\\\expandafter 的工作方式](/files/ba39039bbd2206787ffd469e2de28e34bb9528ae)

以下注释解释处理的各个阶段。

1. TeX 开始处理 `\\uppercase` 并检查必需的左花括号字符（`{`）但检测到一个 `\\expandafter` 命令。
2. 如果我们比较 `\\expandafter` $$\mathrm{T\_1T\_2}$$ 在我们输入的 `\\expandafter{\\jobname}` 我们可以看到 $$\mathrm{T\_1} =$$`{`<sub>记号</sub> 和 $$\mathrm{T\_2} =$$`\\jobname`<sub>记号</sub>。注意这里我们将使用下标 <sub>记号</sub> 来表示 TeX 正在处理整数记号值。
3. `\\expandafter` 读取，然后临时保存 `{`<sub>记号</sub> ，方法是把该整数记号值存入一个内部变量。随后，TeX 会在处理完……之后，把该记号重新插回输入中 `\\jobname` 命令。
4. `\\expandafter` 读取下一个记号， `\\jobname`<sub>记号</sub>，并展开 `\\jobname` 命令。
5. ……的展开结果 `\\jobname` 创建一个临时记号列表，其中包含一串表示……的字符记号 `.tex` 文件名。注意，由 `\\jobname` 生成的所有字符记号
6. 一旦 `\\jobname` 已展开，TeX 会重新插入第 3 步中保存的记号（`{`<sub>记号</sub>）并把它放回输入中。TeX 通过创建另一个包含……的记号列表来做到这一点 *单个* `{`<sub>记号</sub>
7. TeX 现在已经处理完了 `\\expandafter`，从而生成了两个可作为 TeX 输入源的记号列表。TeX 现在恢复处理 `\\uppercase` 但已经把输入配置成由……创建的这两个记号列表 `\\expandafter` 成为……的记号来源 `\\uppercase`——此时它看到 `\\uppercase{`<sub>记号</sub>`<\jobname 的展开结果>`<sub>记号列表（字符）</sub>`}`. `\\uppercase` 现在看到的是一串字符记号，因此可以产生我们想要的结果。
8. 在读取完由……产生的所有字符记号之后 `\\jobname`，TeX 会恢复从先前的输入源（我们的 `.tex` 文件）获取记号，并从那里读取下一个记号：右括号 `}` 这是终止由……处理的记号列表所必需的 `\\uppercase`.

### \\\expandafter 与内部记号列表

临时记号列表是 *至关重要的* ……的组成部分 `\\expandafter`的处理行为：理解这些记号列表的使用和存在，有助于阐明 `\\expandafter` 如何实现其结果，尤其是在尝试编写或理解那些使用多个连续 `\\expandafter` 命令来实现更复杂记号处理形式的宏时： `\\expandafter\\expandafter\\expandafter...`

……的另一个关键要素 `\\expandafter`的行为，尤其是在多个连续的 `\\expandafter` 命令中，是对……的使用 *递归* （在 TeX 软件内部）——这是本文后面要讨论的话题。

为了进一步帮助我们理解临时记号列表，我们再看一个……的例子 `\\expandafter`，这次使用 `\\the` 命令。

#### \\\expandafter 与内部记号列表：示例 2

在这个例子中，我们将看到如何 `\\expandafter` 可用于通过……影响存储在记号寄存器中的记号 `\\toks` 命令。下面是我们将使用的 TeX 原语：

* `**\\count** *register*=*number*`：用于将值 `*number*` 存储到 TeX 位置 `*register*`;
* `**\\toks** *register*={*token list*}`：用于存储 `*token list*` 到记号寄存器位置 `*register*`——保存一串记号以供后用；
* `**\\the** *token*`：一个可展开的 TeX 原语命令，它会处理 `*token*`，不过具体结果取决于所处理的 `*token*` 的性质。 `\\the` 有多种用途：其中之一是排版存储在 TeX 参数或变量（例如寄存器）中的值。 `\\the` 其他用途包括插入记号寄存器中存储的记号副本。在这里，我们将使用 `\\the` 来排版存储在一个 `\\count` 寄存器中的值。

我们先从下面的 TeX 代码开始，把值 `12345` 存入 TeX 的 `\\count` 寄存器 `99`:

```
\\count99=12345
```

如果我们想排版存储在 `\\count99` 中的值，我们可以使用 `\\the\\count99` （或者 `\\number\\count99`).

接下来我们将使用 `\\toks` 命令在记号寄存器中存储一些记号 `99`:

```
\\toks99={\\the\\count99 }
```

存储在记号寄存器中的记号列表 `99` 将包含以下内容：

|             |                                                              |
| ----------- | ------------------------------------------------------------ |
| **TeX 记号值** | **所表示的项目**                                                   |
| 5382        | `\\the`                                                      |
| 7885        | `\\count`                                                    |
| 3129        | `9` （字符码 57，类别码 12），从而得到记号值 $$256 \times 12 + 57 = 3129$$    |
| 3129        | `9` （字符码 57，类别码 12），从而得到记号值 $$256 \times 12 + 57 = 3129$$    |
| 2592        | `<空格>` （字符码 32，类别码 10），从而得到记号值 $$256 \times 10 + 32 = 2592$$ |

注意，由……创建的记号列表 `\\toks99` 会 *不* 包含存储在……中的实际数据值 `\\count99` 因为 `\\toks` 命令不会执行展开：它只是创建记号并将其存储起来。在我们的例子中， `\\the` 不会展开，所以它不会处理 `\\count99`；这里 `\\the` 只是被转换成一个记号（值 5382）并存储在记号列表中。

如果我们想让 `\\toks99` 记号列表包含表示存储在……中的数据的记号 `\\count99` 我们需要某种方式来创建这些记号（使它们可用），以便 `\\toks` 命令能够访问它们。当然 `\\expandafter` 可以为我们做到这一点。如果我们写：

```
        \\toks99=\\expandafter{\\the\\count99 }
```

……的动作/处理 `\\toks` 命令会被“暂时搁置”，而 `\\expandafter` 会导致（强制）展开 `\\the` 而后又作用于 `\\count` 生成一个临时记号列表，其中包含表示存储在……中的数据的字符记号 `\\count99`。有一个微小但重要的点是 `<空格>` 数字后面的字符 `99`：这个 `<空格>` 字符在 TeX 搜索数值时会终止其扫描过程。

这里，……的作用 `\\expandafter` 与……非常相似 `\\jobname` 示例。

1. 读取并保存开头的 `{`<sub>记号</sub>.
2. 读取下一个记号， `\\the`<sub>记号</sub>，它表示一个可展开命令，因此 TeX 会将其展开。 `\\expandafter` 强制展开 `\\the` 然后作用于 `\\count99` 将存储在……中的数据转换为 `\\count` 寄存器 `99` （数字 12345）转换为一个临时记号列表。该列表将包含表示数字 `1`, `2`, `3`, `4` 和 `5`的字符记号——类别码 12 的字符记号。
3. 在展开并处理完 `\\the`之后，TeX 将 `{`<sub>记号</sub> “放回输入中”，并使用由……产生的记号列表 `\\the\\count99` 这样 TeX 就会读取 `\\toks99={`<sub>记号</sub>`<\\the\\count99 的展开结果>`<sub>记号列表（字符）</sub>`}` ，这就产生了我们想要的结果。

以下图示对这一系列事件进行了总结——请从图的底部向上阅读，以跟随处理流程。

![\\\expandafter 的工作方式](/files/0187f3a7f9c4f3d6470322fecfe451ea81a2f832)

1. TeX 开始处理 `\\toks`；它会看到可选的 `=` 符号，然后检查必需的左花括号字符（`{`，或任何类别码为 1 的字符）用于表示记号列表的开始。然而，TeX 检测到一个 `\\expandafter` 命令，于是转而执行它。
2. 如果我们比较 `\\expandafter` $$\mathrm{T\_1T\_2}$$ 在我们输入的 `\\expandafter{\\the\\count99 }` 我们可以看到 $$\mathrm{T\_1} =$$ `{`<sub>记号</sub> 和 $$\mathrm{T\_2} =$$ `\\the<sub>token</sub>`.
3. `\\expandafter` 读取，然后临时保存 `{`<sub>记号</sub> （TeX 会将该整数记号值临时存储在一个内部变量中）。随后，TeX 会在处理完……之后，把该记号重新插回输入中 `\\the`
4. `\\expandafter` 读取下一个记号， `\\the`<sub>记号</sub> 并对其展开。
5. ……的展开结果 `\\the` 通过处理……创建一个临时记号列表 `\\count99`——该记号列表包含一串字符记号，用来表示存储在……中的数据值 `\\count` 寄存器 `99`.
6. 一旦 `\\the` 已展开，TeX 会重新插入第 3 步中保存的记号（`{`<sub>记号</sub>）并把该记号放回输入中。TeX 通过创建另一个包含……的记号列表来做到这一点 *单个* 记号 `{`<sub>记号</sub>.
7. TeX 现在已经处理完了 `\\expandafter` 并产生了两个可作为下一输入源使用的记号列表。TeX 恢复处理 `\\toks99=` 但现在 TeX 已将输入配置为由……创建的两个记号列表 `\\expandafter` 成为……的记号来源 `\\toks`——此时它看到 `{`<sub>记号</sub>`<\\the\\count99 的展开结果>`<sub>记号列表（字符）</sub>`}`. `\\toks` 现在可以访问并存储表示数据值（`12345`）所存储的 5 个字符记号序列 `\\count99`：我们想要的结果。
8. 在读取完由……产生的所有字符记号之后 `\\the\\count99`，TeX 会恢复从先前的输入源（我们的 `.tex` 文件）获取记号，并从那里读取下一个记号：右括号 `}` 这是终止由……保存的记号列表所必需的 `\\toks99={...}`.

## \\\expandafter 的真正工作方式

在本节中，我们将从“底层”来审视 TeX 本身：探究 TeX 内部实现……行为的源代码/函数 `\\expandafter`。细节将以伪 C 代码表示，但对于熟悉其他编程语言的人来说应该都能理解。

下面这张带注释的图解释了 TeX 如何实现 `\\expandafter` 作为一个名为……的更大函数的一部分 `expand()`——驱动 TeX 展开处理的核心函数。在负责实现……的部分中 `\\expandafter` 我们可以看到 [*递归* 行为](https://en.wikipedia.org/wiki/Recursion) 其中会再次调用 `expand()` 函数来处理读入的第二个记号， $$\mathrm{T\_2}$$，对于那些……的情况 $$\mathrm{T\_2}$$ 是可展开的。

虽然这段代码出现在 Knuth 的 TeX 引擎中，但这幅图概述的基本原理适用于所有 TeX 引擎。

![\\\expandafter 在 TeX 内部是如何工作的](/files/a5be8c0d92ad87416e92f7f31d577dad0075defd)

……的首要任务是 `expand()` 确定待展开的命令是宏还是原语，因为宏有一个专门的展开过程，该过程由一个名为 `macrocall()`.

如果待展开的命令是原语， `expand()` 函数会使用当前命令码值（存储在全局变量 `curcmd`）来识别需要处理的具体原语。我们可以在更完整的 `expand()`:

```
    void expand(void)
    {
    //curcmd 是一个全局变量
    if(curcmd != macro) // curcmd < 111
    {
      switch(curcmd)
      {
        case \expandafter: // 处理 \expandafter T1T2 命令
        {
            gettoken(); // 读取记号 T1
            t = curtok; // 将记号 T1 保存到局部变量 t 中
            gettoken(); // 读取记号 T2
            if(curcmd > 100) // 记号 T2 可展开吗？
                expand();    // 是的！T2 可展开：
                             // 通过以下方式对 T2 执行展开
                             // 通过递归调用 expand()
            else
                backinput(); // T2 不可展开：把那个记号
                             // 放回输入中，以便稍后再次读取

            curtok = t ;  // 将全局变量 curtok 恢复为保存的 T1 值
            backinput() ; // 将记号 T1 放回输入中
                          // 放到由 T2 展开产生的记号之前
        }
        break;

        // 处理其他可展开命令的代码
        case “转换为文本”命令: // 下面任意一种 \number、\string、\romannumeral，
                                        // \meaning、\fontname、\jobname
                                        // 它们共享相同的 curcmd 值
        break;

        case \noexpand: // 抑制下一个记号的展开
        ...
        break;

        case \csname:  // 生成一个控制序列名称。
        ...
        break;

        case \the: // 插入一些记号
        ....
        break;

        case “\if... 测试命令”：// 处理 TeX 的条件命令之一：
                                      // \if、\ifcat、\ifnum、\ifdim、\ifodd、\ifvmode、
                                      // \ifhmode、\ifmmode、\ifinner、\ifvoid、
                                      // \ifhbox、\ifvbox、\ifx、\ifeof、\iftrue、\iffalse、
                                      // \ifcase、\ifdefined、\ifcsname、\iffontchar
        ...
        break;

        case “\fi 或 \else”：// 终止当前条件判断
        ...
        break;

        // 以及 TeX 引擎支持的任何其他可展开原始命令
        // TeX 引擎

        }

    }else // 不可展开的原始命令：它是一个宏
        {
             macrocall()
        }
        //……更多代码已删除
    }
```

### TeX 对全局变量的偏爱

也许反映了它的年代和设计所处的时期，TeX 的源代码大量使用了所谓的 [全局变量](https://en.wikipedia.org/wiki/Global_variable)——实际上有数百个之多。就其本质而言，全局变量可以在 TeX 源代码中的任何位置被更改/修改——而对于 Knuth 的 TeX 来说，这是一份单一的庞大文件，包含超过 25,000 行代码和数百个函数。理解 TeX 的工作方式并不总是一件容易的事……

为了处理 `\\expandafter`，TeX 使用一个名为 `gettoken()` 的函数从当前输入中读取记号；该函数会创建一个记号，并设置 TeX 源代码中各处使用的几个关键全局变量的值。由 `gettoken()`的动作更新的两个此类变量，用于 `\\expandafter`:

* `curtok`：（当前记号）刚刚读入的记号的整数值；
* `curcmd`：（当前命令代码）由该记号表示的命令（或字符）的命令代码 `curtok`.

在处理 `\\expandafter`$$\mathrm{T\_1T\_2}$$ 时，TeX 读取记号 $$\mathrm{T\_1}$$ 并将其值（一个整数）临时保存在一个名为 `t`的局部变量中。然后 TeX 读取 $$\mathrm{T\_2}$$ 并检查该记号是否表示一个可展开命令——方法是检查它的命令代码（`curcmd`）是否大于 100。若是，TeX 需要展开由 $$\mathrm{T\_2}$$ 表示的命令，并再次调用函数 `expand()`：这是 *递归* 因为 `expand()` 函数自身调用自身的一个例子。理解展开的递归特性，尤其是在使用 `\\expandafter`时，可以帮助理解多个连续的 `\\expandafter` 命令——即 `\\expandafter\\expandafter\\expandafter...` ——是如何实现其效果的。

如果记号 $$\mathrm{T\_2}$$ 可展开，则展开会发生，并且当对 `expand()` 的递归调用返回时， `\\expandafter` 实现中的代码会将记号 $$\mathrm{T\_1}$$ 重新插回输入中。全局变量 `curtok` 会被重新赋值为保存的记号值——该值存储在局部变量 `t`中，也就是记号 $$\mathrm{T\_1}$$的值——然后调用函数 `backinput()。`

#### 函数 backinput()

顾名思义，这个函数会把一个记号“放回输入中”。为此，TeX 使用全局变量 `curtok` 的当前值来创建一个只包含一个记号的记号列表（其整数值由 `curtok`提供）。TeX 还会安排其输入处理，以确保在适当的时候，这个单记号列表会被 TeX 重新读取，作为其后续输入处理的一部分。请注意，记号 $$\mathrm{T\_1}$$ 会被重新插入 *在展开完成之后*，这确保 TeX 将读取那个重新插入的记号 *在……之前* 当它读取由 $$\mathrm{T\_2}$$.

### 展开产生的记号时

处理宏：macrocall() 函数 `expand()` 如前所述，所有宏以及一些原始命令都是可展开的，所有展开处理都通过 `expand()` 函数进行。然而， `curcmd` 会谨慎地使用 `macrocall()`值（当前命令）来区分可展开原始命令和宏，因为宏展开过程由一个名为 `macrocall()`.

#### 的专用函数处理。宏需要一种专门的展开过程，因为必须以非常特定且严格的方式扫描宏参数和分隔记号；因此，这一过程被委托给一个专门用于此目的的函数：

宏 *展开* 并不等同于宏 *执行*：宏的展开是 TeX 在让宏 *准备好执行之前*所执行的预处理过程。宏的“执行”发生在 TeX 主动读取并处理该宏定义（替换文本）以及其参数中包含的记号时。

#### 宏展开

为了展开一个宏，TeX 首先检查该宏是否带参数；如果是， `macrocall()` 它会非常仔细地扫描输入，寻找将成为宏参数的记号。该过程包括检查用户输入中是否存在宏原始定义中使用的任何分隔记号——宏调用中使用的记号模式必须与存储的定义中包含的记号模式完全匹配。然而，TeX 只是简单地丢弃用作分隔符的记号：它们实际上只是 TeX 用来确定真正将成为宏参数的记号的一种“标点”——也就是用户希望由宏处理的记号。有关分隔记号的更多信息，请参见 [TeX 宏实际上是如何工作的](/latex/zh-cn/geng-duo-zhu-ti/22-how-tex-macros-actually-work-part-4.md).

对于宏原始定义中出现的每个参数（`#1, #2...#9`），TeX 会扫描实际的宏调用，以识别用户提供的哪些记号将归入每个参数（即构成宏的参数）。该过程会生成一个或多个小型记号列表：每个宏参数对应一个。

在检测到任何宏参数并准备好它们的记号列表之后，TeX 会从内存中取出存储的宏定义（替换文本），并安排其输入处理，使得每当 TeX 准备好读取/处理更多记号时，都会从宏的替换文本中读取这些记号，从而执行该宏。在适当的时候，在宏执行过程中，表示宏参数的记号列表会被送入宏替换文本中的正确位置。

再次强调，展开一个宏命令意味着 *移除* 该宏命令（记号）从输入中，并 *用* 将其替换为存储为宏替换文本的记号列表。

想深入了解 TeX 的宏处理，请参阅六部分文章系列 [TeX 宏实际上是如何工作的？](/latex/zh-cn/geng-duo-zhu-ti/01-a-six-part-series-how-do-tex-macros-actually-work.md)

[第1部分](/latex/zh-cn/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md) [第2部分](/latex/zh-cn/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md) [第3部分](/latex/zh-cn/shen-ru-wen-zhang/21-how-does-expandafter-work-tex-uses-temporary-token-lists.md) [第4部分](/latex/zh-cn/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md) [第5部分](/latex/zh-cn/shen-ru-wen-zhang/17-how-does-expandafter-work-a-detailed-macro-case-study.md) [第6部分](/latex/zh-cn/shen-ru-wen-zhang/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.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/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.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.
