TeX 读取源码时,为什么 \ 会成为命令开头,{ 会打开分组?答案就是 类别码(catcode)。每个输入字符都有一个编号,告诉分词器这个字符扮演什么角色。同样的机制也解释了 LaTeX 内部命令中神秘的 @,以及为什么在导言区使用这些内部命令时必须用 \makeatletter … \makeatother 包住。本页会说明这两件事及其使用场景。
类别码是什么
TeX 的处理从 分词(tokenization) 开始:把字符流转换成 记号 流。读取每个字符时,TeX 会查看该字符的 类别码(catcode),这是 0 到 15 的编号,用来决定它在排版中扮演的角色。例如 \ 的 catcode 是 0(escape,命令开始),{ 是 1(开始分组),$ 是 3(数学模式切换)。关键在于,角色由 catcode 决定,而不是由字符本身决定。$ 会进入数学模式,不是因为它是美元符号,而是因为它的 catcode 是 3。
下表给出默认类别码的整体图景。日常最常见的是 0(开始命令)、1/2(分组)、3(数学)、4(表格列分隔)、5(形成段落的行尾)、6(宏参数标记)、7/8(上标与下标)、构成正文的 10/11/12,以及 14(注释)。
| catcode | 角色 | 默认字符 |
|---|---|---|
0 | escape(命令开始) | 反斜杠 \ |
1 | 开始分组 | 左花括号 { |
2 | 结束分组 | 右花括号 } |
3 | 数学模式切换 | 美元符号 $ |
4 | 对齐制表符 | 和号 & |
5 | 行尾 | 回车 / 换行 |
6 | 参数(宏参数) | 井号 # |
7 | 上标 | 插入符 ^ |
8 | 下标 | 下划线 _ |
9 | 被忽略的字符 | 空字符 |
10 | 空格 | 空格与制表符 |
11 | 字母(组成命令名) | a–z, A–Z |
12 | 其他 | 数字、标点以及其他所有字符 |
13 | 活动字符(本身像命令) | 波浪号 ~ |
14 | 注释 | 百分号 % |
15 | 无效字符 | 删除字符 |
catcode 11(字母)与命令名
在各类别中,11(字母) 与命令机制直接相连,格外重要。TeX 的 控制词(control word),也就是像 \section 这样的具名命令,会被识别为 catcode 0 的字符(通常是 \)后接一串 catcode 11 字符。换言之,通常只有 catcode 11 的字符能组成命令名。\section 的名称在 section 处结束,正是因为下一个字符(空格或 {)不是 catcode 11。用户层面的后果,例如控制词后的空格会消失、\LaTeXlogo 会被读成另一个命令,已在“语法规则”页面说明;这里关注其底层的 catcode 机制。
数字和标点默认是 catcode 12(其他),这一点也很重要。a2b 中只有字母能进入命令名,因此 \a2 会被解释为命令 \a 后跟字符 2。反过来说,如果把某个字符的 catcode 改成 11,这个字符就 可以成为命令名的一部分。这一步正是下面 @ 故事的关键。
用 \catcode 改变类别
字符与类别的对应关系并非固定不变;原语 \catcode 可以改写它。语法是 ` \catcode⟨char⟩=⟨number⟩ `:在反引号 ` 后写目标字符,再在 = 右边写新的 catcode。例如 \catcode@=11 ` 表示“从这里开始,把 @` 当作字母(catcode 11)处理”。
% catcode を直接いじる(上級・壊れやすい)/ Touching catcodes directly (advanced, fragile)
\catcode`@=11 % @ を英字に / make @ a letter
\catcode`@=12 % @ を「その他」に戻す / put @ back to “other”这很强大,但也 脆弱,直接使用属于高级操作。改写类别会改变之后的读取方式;如果忘记恢复,会在意想不到的地方破坏文档。与此同时,这个机制也是熟悉功能的基础:\verb 和 verbatim 环境之所以能原样输出源码,是因为内部会 临时把特殊字符(\、{、$ 等)的 catcode 降为 12(其他),让它们不再被解释成命令或分组。下一节的 \makeatletter 可以理解为给这个 \catcode 操作起了一个安全名字的常用包装。
\makeatletter 与 \makeatother
LaTeX 内核和宏包的许多 内部命令 名中都含有 @:例如组装节标题的 \@startsection、查看下一个记号并分支的 \@ifnextchar、表示 0pt 长度的 \p@ 等。它们能在 .sty/.cls 文件中正常定义和使用,是因为在这些文件里 @ 的 catcode 是 11(字母),可以作为命令名的一部分。实际上,\usepackage 和 \documentclass 读取宏包或类时,会自动把 @ 的 catcode 切换为 11。
但在普通文档正文中,@ 是 catcode 12(其他)。所以如果在正文或导言区直接写 \p@,TeX 会把它读成命令 \p 后跟字符 @,而不会到达你想要的内部命令。声明 \makeatletter 会在你 使用或重定义 内部命令的那段区域内,把 @ 重新变成字母;配对的 \makeatother 则把 @ 的 catcode 恢复为默认的 12。顾名思义,它们就是“make @ a letter / make @ other”,内部正是 ` \catcode@=11 ` 和 \catcode@=12 ``。
为什么有这种约定?是为了 把内部命令隔离在普通用户够不到的地方。默认 @ 为 catcode 12 时,只是在写文档的人不可能不小心用自己的命令覆盖 \@startsection 这样的名称。含 @ 的名称等于标着“内部使用”,若要触碰,必须用 \makeatletter 明确“开锁”。
示例与陷阱
典型用途是在导言区 轻微调整 类或宏包定义的内部命令。下面例子把内部宏 \@maketitle(标题区域的排法)包在 \makeatletter … \makeatother 中重新定义。只有包装内部 @ 才是字母,因此 \@maketitle 这个名称会被正确识别为一个命令。
\documentclass{article}
\makeatletter
% 内部命令 \@maketitle を再定義する / redefine the internal \@maketitle
\renewcommand{\@maketitle}{%
\begin{center}
{\LARGE\bfseries \@title}\par
\vspace{1ex}{\large \@author}\par
\end{center}%
}
\makeatother
\title{カテゴリコード入門}
\author{名前}
\begin{document}
\maketitle
\end{document}最大的陷阱是 忘记写 \makeatother。漏掉它会让 @ 继续保持字母状态,破坏后续含有 @ 的文本(如邮箱地址)或某些会特殊处理 @ 的宏包。写了 \makeatletter 后,一定用配对的 \makeatother 关闭;请把二者当作总是成对使用。
另一个误解是在 .sty/.cls 文件内部写 \makeatletter。如前所述,读取宏包或类时 @ 已经是 catcode 11,因此在那里不需要它;多余的 \makeatother 甚至可能干扰后续处理。\makeatletter … \makeatother 只应在 .tex 文档中(通常是导言区)触碰内部命令时 使用。如果能避免直接触碰内部命令,例如用 \renewcommand 改写公开命令,或使用提供相应功能的正规宏包,那会更安全。真正的宏与宏包编写另页说明。