继这个问题,我想问一个更普遍的问题:
什么是类别代码?通过更改它们我能实现什么?
答案1
当 TeX 解析输入时,它会为每个读取的字符分配一个类别代码。TeX 随后如何解释输入取决于字符及其类别代码。程序员可以设置 16 个类别代码,外加一个特殊的内部代码。16 个标准代码从 0 向上编号。类别代码 0 用于转义字符,通常是\
。其余的如下(附典型示例):
- 开始组:
{
- 结束组:
}
- 数学转变:
$
- 结盟:
&
- 行结束
- 宏的参数:
#
- 数学上标:
^
- 数学下标:
_
- 完全被忽视
- 空间
- 字母:字母表。
- “其他”字符 - 其他所有字符:
.
,,,1
:
ETC。 - 活动字符-被解释为控制序列:
~
- 评论开始:
%
- 输入无效:
[DEL]
现在,当 TeX 读取输入时,每个字符都与一个类别代码相关联以生成标记。因此,如果输入为
$ 1^{23}_a $
TeX 内容如下:
- 一个数学移位标记,进入数学模式
- 空格,在数学模式下会被忽略
- 一个“其他”标记
1
,在这里只是简单地排版 - 数学上标标记,这意味着下一个项目将被上标
- 一个 begin-group 标记,
- “其他”标记
2
和3
,在组完成之前无法排版 - close-group 标记
}
,允许 TeX 排版上标 - 数学下标标记,将下一个项目移动到下标位置
- 字母
a
,没有特殊含义,排版 - 空格,再次被忽略
- 一个数学移位标记,然后返回水平模式
当 TeX 决定什么是控制序列,什么不是控制序列时,类别代码通常很重要。如果只使用字母表作为“字母”,则
\hello@
是控制序列,\hello
后面跟着“其他”标记@
。另一方面,如果我制作@
一个字母
\catcode`\@=11\relax
\hello@
然后 TeX 会查找名为 的宏\hello@
。这通常用于 TeX 代码中,以将“代码”宏与“用户”宏隔离开来。因此,您会发现诸如 这样的编程宏\@for
。如果不更改类别代码,这实际上是“隐藏”的。这样做的目的是“保护用户免受自身攻击”:如果您甚至无法获得代码,就很难破解代码!
~
使用类别代码可以实现许多效果。最明显的效果是TeX 世界中普遍使用的不间断空格。这是因为~
的类别代码为 13,因此是“活动的”。当 TeX 读取 时~
,它会以与宏相同的方式查找 的定义~
。在这些情况下,这比使用宏方便得多。
我们可以使用不同的类别代码来创建“私有”代码区域。例如,普通 TeX 和 LaTeX2e 使用@
作为一个额外的“字母”,而 LaTeX3 使用:
和_
。当两者一起使用时(如目前),这有效地将内部 LaTeX3 代码与 LaTeX2e 隔离开来。
逐字材料是另一个类别代码至关重要的领域(如果很复杂的话!)。您不能将逐字材料嵌套在其他任何内容中的原因是,一旦 TeX 分配了类别代码,它就只能部分可逆。任何“忽略”或“注释”的内容都会被丢弃:您无法将其恢复。(使用 e-TeX,您能重新分配类别代码,但任何已经消失的东西都会保持“丢失”状态。
(感兴趣的人请注意)“特殊”类别代码为 16,除其他用途外,还用于测试\ifcat
。在这种情况下,它被分配给不可扩展的控制序列,以便它们不匹配除其他不可扩展的控制序列之外的任何其他内容。
答案2
Knuth 决定保留一些字符用于常见任务,例如启动数学模式或表示上标和下标,而不是定义基本命令。还有其他需求:分组、表示宏参数,最重要的是,为了表达命令而进行转义。
类别代码共有 16 个:
0 = escape
1 = group start
2 = group end
3 = math shift
4 = alignment tab
5 = end of line
6 = parameter
7 = superscript
8 = subscript
9 = ignored character
10 = space
11 = letter
12 = other character
13 = active character
14 = comment
15 = invalid character
通常只有一个字符具有类别 0 至 8
\ { } $ & ^^M # ^ _
(^^M 表示 TeX 放在所有输入行末尾的不可见字符,用于更改可能存在的系统相关字符);唯一性不是必需的,但是是首选:为什么要有两个不同的转义字符以相同的方式起作用?(见下文。)
类别 10 是空格,也是<TAB>
字符,与空格序列没有区别;类别 10 字符在行首被忽略。类别 5 非常特殊:它会转换为空格,除非它后面跟着另一个类别 5 字符,此时它成为命令\par
(这是允许留下一个空白行来结束段落的技巧)。通常,任何连续的类别 10 字符序列都会减少为仅一个,无论它们是空格、制表符还是转换后的行尾字符都没有关系。
所有字母都有第 11 类和标点符号,例如?
、(
、)
)属于第 12 类;这是因为命令名称可以是任何字母序列(最好是第 11 类字符)或一非 11 类别字符,前面是 0 类别字符。当不属于命令名称时,可以打印 11 类别和 12 类别字符;其他所有类别代码并非如此。但是,类别代码 11 或 12 字符也可能不会显示在打印中,因为它在处理过程中被丢弃(例如,关键字或软件包选项、软件包或文件名等)。
之所以将类别 9 和 15 放入 TeX,是因为其中存在“危险”字符(ASCII“null”和 ASCII“delete”),可能会被编辑器误解。实际上,类别 9 还有其他用途:在 LaTeX3 样式文件中,空格被指定为类别 9,以帮助程序员避免可怕的“虚假空格”。
第 14 类是众所周知的%
,它引入注释并让 TeX 忽略行中跟在它后面的所有内容(包括行尾)。
第 13 类非常特殊;Plain TeX 和 LaTeX 内核仅使用一个积极的字符,即~
;活动字符被视为命令,必须具有定义才能使用;LaTeX 定义是
\catcode`~=13
\def~{\nobreakspace{}}
这样输入~
就和书写 一样了\nobreakspace{}
。LaTeX“inputenc”包还使用其他活动字符,例如,ü
被翻译成\"u
。
当我们想要排版逐字TeX 代码中,许多特殊字符被分配了类别代码 12;但是当我们输入 时\verb+\xyz+
,LaTeX 会读取\verb
并准备所有内容进行逐字排版并启动一个组;第一个+
被吞并并被分配到类别 2,因此当它找到第二个时,+
该组终止并且所有分配都恢复为正常分配(包括对 的第 2 类别分配+
):这有点神奇,但它有效,只要\verb+\xyz+
不出现在命令的参数中。
这是一个问题:当 TeX 扫描命令的参数时,它冻结类别代码:当一个字符进入 TeX 时,它会转换成一对(category code, character code)
不再是原始字符的字符,因此类别代码分配不能再修改(嗯,实际上不是,有\scantokens
,但这需要很长的讨论)。
LaTeX 命令\makeatletter
和\makeatother
通过更改 的类别代码@
(通常为 12)来工作;第一个命令将其放入类别 11,以便它可以出现在命令名称中,第二个命令恢复此分配。但如何
\makeatletter
\newcommand{\xyz}{...\@xyz...}
\makeatother
能行吗?有人可能会认为 TeX 在扩展时\xyz
会找到“非法”的命令名\@xyz
。但事实并非如此:就像一个简单的字符会转换成一对数字一样,当 TeX 扫描它时,命令名会变成一个象征性标记,命令的内部表示,与字符及其类别代码无关。
如果我们将类别代码 0 分配给|
,我们可以输入\LaTeX
和|LaTeX
:它们的含义完全相同。但是,让不同的字符共享相同的类别代码 4 可能有助于在表格中的小数点分隔符处对齐小数。如果我们分配.
类别代码 4,我们可以将小数输入为 ,123.456
而 LaTeX 会将其解释为123&456
产生二表格单元格对于最终用户来说是一个整体;不过,在表格列结构的定义中需要一些技巧。
答案3
这不是一个简单的话题,我只能请您参考 TeXBook 来了解更多详细信息,但这里有一个简短的概述。
TeX 从文件读取的每个字符都有两个与之关联的数字。一个“字符代码”和一个“类别代码”。TeX 不认识字形 - 只认识数字 - 这是它的优势之一。您可以将字体表视为查找列表。如果您给它一个数字,TeX 将查看列表并打印恰好位于该位置的字形。
第二个数字是“类别代码”。TeX 使用它来智能地解析输入。例如,TeX 需要知道{
文档的某个部分是否出现了左花括号,以便它可以查找结束括号等等。当然,这可以是硬编码的,但 Knuth 选择将其抽象化,这样任何字符,只要它有适当的类别代码,都可以使用。
考虑一下您可能希望用方括号替换花括号 []
,这可以通过以下简单的代码实现:
\catcode `[=1
\catcode `]=2
\def\test[This is a test]
\bye
如果不进行任何更改就尝试catcode
,它将会因失控定义错误而失败。
类似地,\
可以重新定义反斜杠字符以用于逐字文本。
\catcode `[=1
\catcode `]=2
\def\test[This is a test]
\catcode `*=0
*def*test[This is another test]
*test
\bye
在最后一个例子中,\
您可以输入*
。通过 pdfTeX 运行 MWE。作者实际上不需要它们,但它们对于软件包开发人员来说非常有价值。
答案4
这里已经有很好的答案了,所以让我仅展示一个小例子,说明通过更改 catcode 可以做什么。假设您在像和这样的表中有数字,12.3
并且8.45
您想将它们全部填充到相同的宽度,以使它们排列整齐。 (让我们暂时忽略有几种方法。)您可以通过0
在之后12.3
和之前添加不可见的来做到这一点8.45
。不可见的由(或)0
提供。现在,使用它来输入有点不愉快,因为它会弄乱您的源代码布局。但您可以使本地活动并赋予它含义:\phantom{0}
\phantom0
_
\phantom0
\catcode`_=\active
\def_{\phantom0}
现在您可以在表格中使用12.3_
和。您也可以使用其他字符,例如。由于在数学模式下用于下标,因此这仅在您不需要下标且上述定义处于活动状态时才有效。_8.45
!
_