\makeatletter
使用/ 的原因\makeatother
已在很多地方讨论过,例如在这个问题中。
但是我认为,这种玩弄“@”字符的技巧本身就是一种黑客行为。临时重新定义 @ 的 catcode 不会导致问题吗?这可能不是实际最常见的使用场景,但如果一段由\makeatletter
/包含的代码\makeatother
使用宏\somemacro
,并且我们习惯于将此宏应用于某些没有括号的参数,如\somemacro@
,这不会破坏代码吗?此外,考虑到这种可能性,是否存在可能发生此类破坏的非人为用例?
(是的,这个问题可能属于“理论”领域,但熟悉解析问题和编译使我希望看到对此的讨论/记录。)
答案1
当你想直接地使用名称中带有 的宏@
(从现在开始为 at-macro),那么您必须确保 被@
视为字母,即调用必须在\makeatletter
声明之后。
然而,在这种情况下,不需要使用以 at-macros 定义的普通宏。让我们看一个例子。
LaTeX内核定义\item
如下:
\def\item{%
\@inmatherr\item
\@ifnextchar [ {\@item}{\@noitemargtrue \@item[\@itemlabel]}%
}
并且当 有效时它也会这样做\makeatletter
。当\def
执行时,TeX 会将\item
和 的替换文本存储在内存中,但其形式与构成所用控制序列名称的实际字符无关。因此,当\item
出现在文档中时(通常当有效时),其扩展包含 at-macros 的事实对于扩展过程而言无关紧要:TeX 从其内存中检索内部形式\makeatother
的含义,当第一个控制序列必须扩展时也会发生同样的情况。\item
\@inmatherr
相反,如果您需要修改的定义\item
以便在开头添加一些内容,则必须正确进行:
\makeatletter
\def\item{%
\typeout{Doh! An item!}%
\@inmatherr\item
\@ifnextchar [ {\@item}{\@noitemargtrue \@item[\@itemlabel]}%
}
\makeatother
因为您指的是定义主体中的 at-macros。如果没有\makeatletter
,该行将\@inmatherr\item
被解析(我使用 • 将一个标记与另一个标记分开)
\@•i•n•m•a•t•h•e•r•r•\item
也就是说,有 11 个标记,而不是 2 个!这是因为在正常情况下@
不是字母:当 TeX 因为找到反斜杠而寻找命令名称时,任何非字母都会立即停止扫描,命令名称将由那非字母。
什么是字母?类别代码为 11 的任何字符扫描正在进行时。但是,我再说一遍,一旦扫描完成,控制序列就会以独立于类别代码的内部格式进入 TeX。
通过查看您的示例\somemacro@
,可以“轻松”区分这两种情况:
\makeatletter
\somemacro@ % <- ONE token
\makeatother
\somemacro@ % <- TWO tokens
因此,如果您在文档的正常位置定义了\somemacro@
和 (作为 at-macro) ,则组合将扩展,而 at-macro 不会扩展。实际上,控制序列名称的扫描将因 而停止,而 不是文档中的字母(当然,如果您还没有这样做)。\somemacro
\somemacro@
\somemacro
@
\makeatletter
以下情况可能会引起疑问:
\makeatletter
\def\somemacro@#1{Do something smart with #1}
\def\somemacro#1{\somemacro@{(#1)}}
\makeatother
和输入(在正常情况下,即\makeatother
)
\somemacro@
现在这些二标记:\somemacro•@
,因此@
将是参数\somemacro
,最终结果将是打印
使用 (@) 做一些聪明的事情
需要一段时间来消化这些技巧;但是一旦人们意识到 TeX 通过读取输入文件来处理它所形成的标记,并在将其转换为内部格式(这对于用户来说无关紧要)后对其进行处理,事情就会开始变得更加顺利。