类别码与 \makeatletter

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角色默认字符
0escape(命令开始)反斜杠 \
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)处理”。

latex
% catcode を直接いじる(上級・壊れやすい)/ Touching catcodes directly (advanced, fragile)
\catcode`@=11   % @ を英字に / make @ a letter
\catcode`@=12   % @ を「その他」に戻す / put @ back to “other”

这很强大,但也 脆弱,直接使用属于高级操作。改写类别会改变之后的读取方式;如果忘记恢复,会在意想不到的地方破坏文档。与此同时,这个机制也是熟悉功能的基础:\verbverbatim 环境之所以能原样输出源码,是因为内部会 临时把特殊字符(\{$ 等)的 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 这个名称会被正确识别为一个命令。

document.tex
\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 改写公开命令,或使用提供相应功能的正规宏包,那会更安全。真正的宏与宏包编写另页说明。