> 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/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.md).

# \expandafter 是如何工作的：连续 \expandafter 命令的详细研究

&#x20;[第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)&#x20;

## 案例研究：理解 \expandafter\expandafter\expandafter...

高级宏，例如 LaTeX 宏包中的那些，通常会大量使用连续的多个 `\\expandafter` 命令来执行复杂的令牌“杂耍”。对于我们大多数人来说，这类宏可能难以理解，也令人望而生畏。在本节中，我们将回顾 TeX 处理连续 `\\expandafter` 命令：

```
\expandafter\expandafter\expandafter...
```

为了便于说明，我们将为每个 `\\expandafter`添加下标——以表明我们指的是哪一个：

```
        \expandafter1\expandafter2\expandafter3...
```

此外，我们将把每个 `\\expandafter` 处理的令牌的记号扩展为使用 $$\mathrm{T^i\_1}$$ 和 $$\mathrm{T^i\_2}$$ 表示令牌 $$\mathrm{T\_1}$$ 和 $$\mathrm{T\_2}$$ 由 `\\expandafter` 读入，并带有下标 `i`: `\\expandafter`<sub>i</sub> $$\mathrm{T^i\_1T^i\_2}$$。我们还假设有两个令牌 $$\mathrm{T\_X}$$ 和 $$\mathrm{T\_Y}$$ 跟在 `\expandafter<sub>3</sub>` 之后，因此我们的输入看起来像这样： `\expandafter<sub>1</sub>\expandafter<sub>2</sub>\expandafter<sub>3</sub>` $$\mathrm{T\_{X}T\_{Y}}$$.

当 TeX 开始处理这个输入时，对于 `\expandafter<sub>1</sub>` 它将看到

* $$\mathrm{T^1\_1} =$$`\expandafter<sub>2</sub>` ，它会被保存以供稍后 *重新插入* 回到输入中
* $$\mathrm{T^1\_2} =$$`\expandafter<sub>3</sub>`，它被展开

如果我们回顾一下之前关于 [TeX 内部代码](/latex/zh-cn/shen-ru-wen-zhang/20-how-does-expandafter-work-from-basic-principles-to-exploring-tex-s-source-code.md) 实现 `\\expandafter`，正是在展开 `\expandafter<sub>3</sub>` 时，我们看到了递归的发生。为了处理 `\expandafter<sub>1</sub>` TeX 已经调用了其内部函数 `expand()`，因此为了处理（展开） `\expandafter<sub>3</sub>` TeX 正在进行一次 *第二个* 对 `expand()`的调用——而且还是从 `expand()` 函数本身内部发出的。

对于 `\expandafter<sub>3</sub>` 我们有

* $$\mathrm{T^3\_1 = T\_X}$$，它被保存以供稍后 *重新插入* 回到输入中
* $$\mathrm{T^3\_2 = T\_Y}$$，我们假设它是可展开的

进一步假设 $$\mathrm{T\_Y}$$ 的展开会产生如下令牌序列： $$\mathrm{{T^1\_Y}{T^2\_Y}{T^3\_Y}}\cdots\mathrm{T^N\_Y}$$。我们现在已经到达由命令序列 `\expandafter<sub>1</sub>\expandafter<sub>2</sub>\expandafter<sub>3</sub>` $$\mathrm{T\_{X}T\_{Y}}$$ 开始的展开过程的末尾，TeX 随后开始“回溯”最初由 `\expandafter<sub>1</sub>`.

在处理 `\expandafter<sub>3</sub>` 之后，TeX 在其内存中保存着一个令牌列表，其中包含 $$\mathrm{T\_Y\text{: }{T^1\_Y}{T^2\_Y}{T^3\_Y}}\cdots\mathrm{T^N\_Y}$$。TeX 现在开始重新插入在处理 `\\expandafter` 命令：

1. TeX 先重新插入令牌 $$\mathrm{T\_X}$$ ，由 `\expandafter<sub>3</sub>`. $$\mathrm{T\_X}$$ 被重新插入 *到* 展开结果的前面 $$\mathrm{T\_Y}$$，从而得到一个令牌序列： $$\mathrm{{{T\_X}T^1\_Y}{T^2\_Y}{T^3\_Y}}\cdots\mathrm{T^N\_Y}$$.
2. 不过，我们仍然需要完成由 `\expandafter<sub>1</sub>` 启动的过程，它保存了代表 `\expandafter<sub>2</sub>`
3. TeX 组装好的最终令牌序列，准备在 TeX 处理的下一阶段读取的是 `\expandafter<sub>2 (token)</sub>` $$\mathrm{T\_{X}T^1\_{Y}T^2\_{Y}T^3\_{Y}\cdots T^N\_Y}$$
4. TeX 现在已经完成了“第一轮”处理，并切换为读取它所生成的令牌列表序列——该序列以 `\expandafter<sub>2 (token)</sub>` 开头，TeX 接着对其进行处理。对于 `\expandafter<sub>2</sub>` 我们有
   * $$\mathrm{T^2\_1} =\ \mathrm{T\_X}$$ ，它会被保存以供稍后 *重新插入* 回到输入中
   * $$\mathrm{T^2\_2} = \mathrm{T^1\_Y}$$ ，它是由展开 $$\mathrm{T\_Y}$$后产生的第一个令牌；如果它可展开，就会被展开
5. 如果我们假设令牌 $$\mathrm{T^1\_Y}$$，即展开 $$\mathrm{T\_Y}$$后产生的第一个令牌，会展开为 $$\mathrm{{T^A\_{Y1}}{T^B\_{Y1}}{T^C\_{Y1}}}$$ 那么，在 TeX 重新插入 $$\mathrm{T\_X}$$之后，TeX 需要重新处理的令牌序列将是： $$\mathrm{{T\_X}{T^A\_{Y1}}{T^B\_{Y1}}{T^C\_{Y1}}{T^2\_Y}{T^3\_Y}\cdots{T^N\_Y}}$$

   我们可以将其重新表述为

   $$\mathrm{T\_X}\text{\<expansion of the first token in }\mathrm{T\_Y}\text{>\<remaining tokens in }\mathrm{T\_Y}\text{>}$$

下图说明了由 TeX 代码

```
    \expandafter1\expandafter2\expandafter3TXTY
```

![展示 TeX 处理多个 \expandafter 命令的图像](/files/7fafa79f1cff4b8953f2d6acd5901a65bbb0e015)

### 从理论到实践

作为示例，我们将定义以下宏作为 $$\mathrm{T\_X}$$ 和 $$\mathrm{T\_Y}$$

* $$\mathrm{T\_X}=$$`\\foo` 其中我们定义 `\\foo` 的通常用法写成 `\def\foo#1{\textbf{#1}}`
* $$\mathrm{T\_Y}=$$`\bar` 其中我们首先定义 `\def\abc{Hello}`, `\def\xyz{, World!}` 然后 `\def\bar{\abc\xyz}`

我们将使用以下代码片段来演示前面的分析：

\expandafter\expandafter\expandafter\foo\bar

根据我们的讨论， `\expandafter<sub>1</sub>\expandafter<sub>2</sub>\expandafter<sub>3</sub>` $$\mathrm{T\_X}\mathrm{T\_Y}$$ 的结果会产生如下形式的令牌序列：

$$\mathrm{T\_X}\text{\<expansion of the first token in }\mathrm{T\_Y}\text{>\<remaining tokens in }\mathrm{T\_Y}\text{>}$$

其中具体序列取决于令牌 $$\mathrm{T\_Y}$$的性质。如果我们使用 $$\mathrm{T\_X}=$$`\\foo` 和 $$\mathrm{T\_Y}=$$`\bar`插入我们的示例命令，它被定义为 `\def\bar{\abc\xyz}`，我们会看到：

* 在 $$\mathrm{T\_Y}$$ 是 `\abc` 中，第一个令牌及其展开是一个字符令牌序列： `Hello`
* 在 $$\mathrm{T\_Y}$$ 中的剩余令牌是代表 `\xyz`.

如果我们把这些信息代入我们的“分析”，就会得到

$$\begin{align\*} &\mathrm{T\_X}\text{\<expansion of the first token in }\mathrm{T\_Y}\text{>\<remaining tokens in }\mathrm{T\_Y}\text{>}\ &=\text{foo}*{\text{token}}\text{\<expansion of \abc>}*\text{token list (characters)}\text{xyx}*\text{token}\ &=\text{foo}*\text{token}\text{Hello}*\text{token list (characters)}\text{xyx}*\text{token}\\\[10pt] \end{align\*}$$

请注意，下标 <sub>记号</sub> 和 <sub>令牌列表（字符）</sub> 用于强调（提醒我们）TeX 读取的是 *整数令牌值*，而不是 *文本字符*，因此没有必要在 `\\foo`之后显示任何空格字符或其他分隔符：这些分隔符早已被处理或丢弃；在这里，我们完全处于 TeX 的内部世界——令牌列表和整数令牌值。

当 TeX 处理由我们的 `\\expandafter` 命令产生的令牌列表序列时，它将排版 `**H**ello, World!`——只有 `H` 会以粗体排版。我们可以通过编写等效的 TeX 代码来实现相同的结果 `\foo Hello\xyz`。请注意， `\\foo` 的定义使用了一个参数；因此， `\\foo` 会吸收单个 `H` 字符令牌作为它的参数，而剩余的字符令牌（`ello`）不变。

**注意：**

* 写出 `\foo\bar` 会产生截然不同的输出： `\bar` 的令牌会被用作 `\\foo` 的参数，从而排版出 **`Hello, World`**——所有内容都会以粗体排版。
* 写出 `\expandafter\foo\bar` 会导致 `\bar` 被展开，从而产生两个令牌： $$\text{abc}*\text{token}\text{xyz}*\text{token}$$。然后，在 $$\text{foo}*\text{token}$$ 被 `\\expandafter` TeX 处理令牌序列 $$\text{foo}*\text{token}\text{abc}*\text{token}\text{xyz}*\text{token}$$ 这会排版为 `**Hello**, World`——只有 **`Hello`** 会以粗体排版。在这里，单个令牌 $$\text{abc}*\text{token}$$ 被当作宏令牌的参数来处理 $$\text{foo}*\text{token}$$，而令牌 $$\text{xyz}\_\text{token}$$ 保持不变，其内容以当前字体排版。

## 关于 \expandafter 和带参数宏的说明

在使用 `\\expandafter` 为了强制展开宏，值得了解宏展开是如何工作的——尤其是对于带参数的宏。在 TeX 能够运行一个宏——也就是读取并处理宏定义中包含的令牌——之前，TeX 需要先通过执行初始的宏展开过程让该宏“准备运行”。如果某个宏的定义使用了参数（`#1`, `#2`, ... `#9`），宏展开过程的一部分要求 TeX 扫描输入，寻找由用户提供的参数所构成的令牌：这些参数令牌会从输入中被吸收（移除）。在宏展开期间，TeX 从输入中读取并吸收令牌，创建小型令牌列表，每个参数对应一个列表；随后在 TeX 执行宏时，这些令牌列表会被插入到宏主体中的相应位置。宏展开的最后一步是让 TeX 定位存储在内存中的宏定义，并将该位置设为 TeX 读取下一批输入令牌的来源。当 TeX 开始读取并处理这些令牌、并将之前创建用于存储参数的令牌列表馈入其中时，宏的执行便开始了。

### 分隔符令牌也会被吸收

如果原始宏定义还使用了充当分隔符的令牌，TeX 还需要将原始宏定义与用户对该宏的使用（调用）进行比较，寻找并匹配分隔符令牌。一旦匹配/定位到这些令牌，分隔符令牌随后就会被忽略，因为它们唯一的作用是充当“标点”，帮助 TeX 挑出并识别那些最终构成各个参数的实际令牌。

&#x20;[第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/18-how-does-expandafter-work-a-detailed-study-of-consecutive-expandafter-commands.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.
