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

# TeX 宏究竟如何工作：第 1 部分

[第 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 宏（如 LaTeX 命令）实际上是如何工作的——在最基础的层面上，在真正的 TeX 引擎软件内部。我们不会仅仅依赖 *仅仅* 一整套旨在展示 TeX 各种特性、边界情况和行为的示例宏，而是直接查看 TeX 本身，看看 *如何* 和 *为什么* 它的宏编程方法为什么会这样工作。

要实现我们的目标，我们首先需要讨论一些相当底层的话题；起初，这些内容看起来可能与文档排版任务相距甚远。希望在深入探究之后，你能建立起一个更好的理解基础，最终为你节省大量时间，也许还能减少挫败感。

### TeX 编程语言：你有这种感觉吗？

把 TeX 编程语言描述为有些神秘莫测并不过分，因为它确实如此——至少按当今大多数主流编程语言的标准来看是这样。当你开始学习 TeX/LaTeX，尤其是当你想编写非平凡的宏时，你会很快遇到诸如类别码、记号/记号化以及命令或宏的“展开”等概念。这一连串概念很可能显得相当陌生，也许会让你感到困惑，有时甚至会有些沮丧，因为 TeX/LaTeX 那些近乎难以理解的错误信息并不总能帮助你走向成功。

## 那么，我们从哪里开始？从类别码开始。

TeX 引擎属于一种被称为 [编译器](https://en.wikipedia.org/wiki/Compiler)：这类程序输入一个用 *源* 语言编写的文件，并且 *编译* （转换）成一个用 *目标* 语言编写的输出文件。更具体地说，TeX 是一种 *文档编译器*。对于 TeX 引擎（编译器）来说，输入文件是用 TeX 排版语言编写的，而目标是一个用另一种“语言”编写的输出文件，例如 [DVI](https://en.wikipedia.org/wiki/Device_independent_file_format) 或 PDF——尽管我们对“语言”这个概念的使用有点宽泛。

让我们更仔细地看看用于编写 TeX 文件的源“语言”，也就是输入“语言”。一个 `.tex` 文件最终只是一长串字符（包括换行字符）：其中包含待排版的文本，夹杂着 `\`, `}`, `$`, `[` 和各种各样的字符，它们似乎可以以近乎无限的组合出现。任何不使用 TeX/LaTeX 的人看到一个典型的 `.tex` 文件时，很可能会把它看成一团相当混乱的字符，几乎看不出任何文件结构。LaTeX 宏包当然在某种程度上会在 .tex 输入文件上“施加”一些基本结构。然而，在……之间 `\begin{document}` 和 `\end{document}` 那里要放什么，则由文档作者决定。如果你查看 `.tex` 使用 Knuth 原始的 Plain TeX 宏包编写的文件，你会发现文档结构几乎完全不存在。

因此，总的来说，TeX 输入文件看起来可能相当没有结构：它似乎是待排版内容与指导这些内容排版的说明（命令）的任意混合。TeX 究竟如何才能理解一个典型的 `.tex` 输入文件：如何把那团涌入的字符杂乱信息过滤成可执行的指令，以及需要排版的内容？

### 过滤这团乱麻：认识类别码

任何不了解 TeX 的人，看到一个 `.tex` 文件时，可能会识别出某些字符，例如 `$` 并知道那是货币符号，或者看到一个 `&` 并将其识别为与号。该观察者会为每个字符推断出一个 *和含义* 含义——这种含义基于该字符在人类交流中的作用。此外，他们还可能看到诸如 `a`, `e`, `或` 并知道它们被归类为元音，而其他如 `b`, `c` 或 `d` 则被归类为辅音。作为人类，我们在记忆中有一种内建的查找表，通过它我们为看到的每个字符赋予含义——这种含义基于该字符在我们能够交流的语言中所扮演的角色。

要处理一个 `.tex` 文件，TeX 软件也必须查看输入中的每一个字符，它同样需要为 *和含义* 它“看到”的每一个字符赋予一个含义。然而，TeX 只是一个处理文本的软件化机器——这些文本以整数序列（字符码）的形式存储在输入文件中。作为一台机器，TeX 必须被编程并提供相关数据，以便告诉它如何确定它正在“查看”的字符的含义，以及随后需要对它做什么。TeX 是如何做到这一点的？

答案就是 TeX 独有的概念之一： *类别码，* 它共有 16 种，从 0 到 15。就 TeX 而言，它预期会在一个 `.tex` 文件中看到的每个字符，都有一个所谓的 *类别码* 预先分配给它。在 TeX 软件内部有一种“查找表”，列出了 *当前* 分配给 TeX 可能在输入 .tex 文件中看到的每个字符的类别码。你应该把 TeX 的类别码理解为为每个单独字符分配一个 *和含义* 给 TeX 必须检查（扫描）的输入流中的每个单独字符分配一个

要排版你的文档，TeX 引擎必须读取（扫描）每一个字符，但 TeX 立即关注的并不是实际字符（字符码）：在扫描输入时，字符的 *类别码* 在扫描输入时更重要。一个字符的 *当前* 类别码决定了 *当前含义* 该字符的 *在 TeX 读取它时的*：该类别码决定了 TeX 将如何处理/处理每个字符——我们稍后会解释为什么我们说“当前类别码”和“当前含义”。正是通过类别码，TeX 才能过滤涌入的字符杂乱信息，从而区分用于排版的字符（内容）和构成待处理指令的字符——*命令* 即 TeX 需要执行的指令。

下表列出了这 16 个类别码：它们各自表示什么，以及通常会被分配到每个类别中的字符示例。

| **类别码** | **说明**                             | **标准 LATEX/TEX**              |
| ------- | ---------------------------------- | ----------------------------- |
| 0       | 转义字符——告诉 TEX 开始寻找命令                | `\`                           |
| 1       | 开始一个组                              | {                             |
| 2       | 结束一个组                              | }                             |
| 3       | 数学切换——切换进入/退出数学模式                  | $                             |
| 4       | 对齐制表符                              | &                             |
| 5       | 行尾                                 | ASCII 码 13（`\r`)              |
| 6       | 宏参数                                | #                             |
| 7       | 上标——用于排版数学公式： `$y=x^2$` $$y=x^2$$  | ˆ                             |
| 8       | 下标——用于排版数学公式： `$y=x_2$` $$y=x\_2$$ | \_                            |
| 9       | 被忽略的字符                             | ASCII 0 `<空>`                 |
| 10      | 空格符                                | ASCII 码 32（空格）和 9（制表符）        |
| 11      | 字母                                 | A...Z、a...z（以及数千个 Unicode 字符） |
| 12      | 其他                                 | 0...9，加上 ,.;?" 以及其他许多字符       |
| 13      | 活动字符                               | 用于创建单字符宏的特殊类别代码，例如 ˜          |
| 14      | 注释字符——忽略从此处开始直到行尾的所有内容             | %                             |
| 15      | 无效字符，不允许出现在 .tex 输入文件中             | ASCII 码 127（`DEL`)            |

类别码的使用是 TeX 过滤输入字符流的核心机制，使它能够理解你的输入并确定：

* 构成待排版文本的字符；
* 用于分隔应排版为数学公式的内容；
* 作为待处理或执行的命令名称的字符序列；
* ……以及许多其他排版操作。

起初，你可能会认为每个字符的类别码（含义）是一种固定分配：不可改变，并且永久地烙印在 TeX 软件内部结构中，但事实并非如此。如前所述，TeX 维护着一个内部查找表，用于存储 *当前* 分配给每个字符——我们特意说 *当前分配* ，因为任何尚未读入的字符的类别码都可以通过使用一个称为 `\catcode`。

如果你主要关心的是使用 LaTeX 来“把事情做完”，那么你很可能并未直接接触过类别码，除非是在你见过的错误信息中。不过请放心，类别码是 TeX 引擎运作的核心组成部分：它使 LaTeX（以及 LaTeX 宏包）真正能够完成对文档的排版工作。

当你的 TeX 引擎启动（“[自举](https://en.wikipedia.org/wiki/Bootstrapping)”）时，它会使用一套默认的字符到类别码的分配，但通过 `\catcode` 命令，这些默认值可以被核心 LaTeX 代码（宏）和/或你加载的 LaTeX 宏包更改——当然也可以由你自己的 TeX 代码或宏来更改。不过，随着时间推移并经由传统/使用习惯，分配给特定类别码的某些字符已被接受为“标准”，如果你希望文档具有可移植性并能轻松与同事或其他用户共享，那么遵循这些标准当然是值得提倡的。例如， `\` 字符被分配类别码 0，用于表示 TeX/LaTeX 命令的开始——参见 [上表](#tbl-0).

### 读取（扫描）输入

当 TeX 读取（扫描）输入文件中的下一个字符时，它做的第一件事就是查看它的类别码，所以让我们更仔细地看看 TeX 读取一行典型输入时会发生什么。

假设我们有一个 .tex 文件，其中包含文本 `Hello World \jobname` 在某个段落中某处。如果我们查看 `.tex` 文件，借助一个 [十六进制编辑器](https://en.wikipedia.org/wiki/Hex_editor)，我们会看到字符序列 `Hello World \jobname` 在我们的 `.tex` 文件中只是一个整数序列，或者说 *字符码*，如下截图中所示的十六进制序列：

`48, 65, 6C, 6C, 6F, 20, 57, 6F, 72, 6C, 64, 20, 5C, 6A, 6F, 62, 6E, 61, 6D, 65, 20`

![TeX 文件中的十六进制字符码](/files/dd3696c97d7286ee819358246e2aa2280412c539)

如果我们从十六进制（基数 16）转换为十进制（基数 10），字符码序列就是：

![TeX 文件中的十进制字符码](/files/c0c59a77484764c5ab2cafa20270fcf42b89c2d8)

我们也知道，对 TeX 来说，每个字符都有一个对应的类别码；因此，根据 [上表](#tbl-0) 我们知道，以下默认的类别码分配（大概）也在使用中：

![TeX 类别码](/files/dc13dc8a25a4aef221df5aa9bb25cc4507423be4)

因此，对于 TeX 来说，输入文件中的每个字符都由 *路径元素：* 数值表示——即它的字符码和类别码：

![字符码及对应的 TeX 类别码](/files/25cae8574a461572f3de513df68478164a975df6)

到目前为止，我们只是在考虑 TeX 处理你的文件的第一阶段：扫描单个字符。那么 TeX 实际上会如何处理这些字符码和类别码的组合？一旦 TeX 扫描了一个单独字符并查找到了它对应的类别码，TeX 究竟如何利用这些信息来“过滤”涌入的字符？

## 第 2 部分

在第 2 部分，我们将更仔细地看看 TeX 是如何读取你的输入的：假装自己是 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://ayakaleaf-pro.ayaka.space/latex/zh-cn/geng-duo-zhu-ti/19-how-tex-macros-actually-work-part-1.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.
