LaTeX 中有几个命令已经变得更加强大,因此它们可以在其他命令的参数中使用,从而移动它们的内容(问题的解释在脆弱命令和坚固命令之间有什么区别?)。LaTeX 通常只为此定义两个命令,一个调用\protect
ed 辅助命令,另一个执行实际操作。作为示例,让我们看一下 的定义\textbf
:
\textbf:
macro:->\protect \textbf
\textbf :
\long macro:#1->\ifmmode ... \fi
这非常简单易懂。另一个默认情况下不受保护的命令是\(
。不过,该fixltx2e
软件包提供了一个强大的命令:
\(:
macro:->\x@protect \(\protect \(
\( :
macro:->\relax \ifmmode \@badmath \else $\fi
事情开始变得令人困惑。\textbf
和\(
的定义非常相似,都将一些东西包装到 中\ifmmode ... \fi
。 因此,人们会认为\(
以相同的方式保护它们:只需在其前面加上 即可\protect
。 但是,\(
使用\x@protect
和\@x@protect
的定义有些奇怪:
\x@protect:
macro:#1->\ifx \protect \@typeset@protect \else \@x@protect #1\fi
\@x@protect:
macro:#1\fi #2#3->\fi \protect #1
那么,有人能解释一下为什么\x@protect
在这里使用 以及它究竟是如何工作的吗? 与简单地使用 相比,有什么优势吗\protect
?
答案1
\(
我们不希望在辅助文件中写入时在后面添加空格。这就是它\x@protect
的目的。它永远不应该在宏定义或文档中使用它。
让我们看看会发生什么\(
(顺便说一下,它在内核中已经稳定运行了好几年)。
我将使用一行来表示扩展步骤;a•
表示宏名称中的空格。a 中的部分《》
表示已经发送到胃中的标记。
正常排版
这里\protect
是\@typeset@protect
,与 相同\relax
。
\(
\x@protect\(\protect\(•
\ifx\protect\@typeset@protect\else\@x@protect\(\fi\protect\(•
\protect\(•
《\protect》\relax\ifmmode\@badmath\else$\fi
这里很清楚地说明了发生了什么。
写入文件或执行\protected@edef
这里\protect
不是\@typeset@protect
。的定义\@x@protect
是
% latex.ltx, line 988:
\def\@x@protect#1\fi#2#3{%
\fi\protect#1%
}
现在扩展
\(
\x@protect\(\protect\(•
\ifx\protect\@typeset@protect\else\@x@protect\(\fi\protect\(•
\@x@protect\(\fi\protect\(•
\fi\protect\(
\protect\(
( 的扩展\fi
为空。)此时执行取决于\protect
实际是什么(\noexpand\protect\noexpand
或)。请注意,在写入文件时,\string
TeX 不会在后面添加空格。\(
为什么?
正如开头所说,我们不希望空格在控制符号(例如\(
或\@
(或活动字符))之后悄悄出现。如果遵循与控制字相同的路径,结果将是\(•
不希望的书写。
看移动参数和 \protect:理解定义的其他重要方面\protect
。
仅出于完整性考虑,\DeclareRobustCommand
定义为\@star@or@long\declare@robustcommand
;第一个宏测试*
(如果存在则使用它),设置一个条件,该条件将在稍后使用\new@command
,与我们无关。更重要的是看看\declare@robustcommand
做了什么:
% latex.ltx, line 963:
\def\declare@robustcommand#1{%
\ifx#1\@undefined\else\ifx#1\relax\else
\@latex@info{Redefining \string#1}%
\fi\fi
\edef\reserved@a{\string#1}%
\def\reserved@b{#1}%
\edef\reserved@b{\expandafter\strip@prefix\meaning\reserved@b}%
\edef#1{%
\ifx\reserved@a\reserved@b
\noexpand\x@protect
\noexpand#1%
\fi
\noexpand\protect
\expandafter\noexpand\csname
\expandafter\@gobble\string#1 \endcsname
}%
\let\@ifdefinable\@rc@ifdefinable
\expandafter\new@command\csname
\expandafter\@gobble\string#1 \endcsname
}
驯服怪物其实并不难。首先测试参数是否已定义,如果是前者,则会发出“重新定义”信息消息。
现在我们必须区分两种情况:控制符号(示例\?
)和控制字(示例\foo
)的行为将有所不同。
宏\reserved@a
将包含参数的字符串化版本,这里没有什么特别的。然后\reserved@b
被定义为扩展为#1
,然后\edef
以特殊的方式重新定义(用)。让我们看看这两种情况的结果
\? → \?
\foo → \foo•
(和前面一样,•
表示空格;但是,\reserved@a
和中存储的结果\reserved@b
只是字符串)。
#1
现在使用来定义\edef
。因为\?
我们将得到
\x@protect\?\protect\?•
因为\foo
我们会得到
\protect\foo•
(现在标记是控制序列,•
表示空格在名字里)
然后将定义名称中带有尾随空格的序列,使用\new@command
。
这表明了为什么\x@protect
不应该在任何地方使用:它没有任何用处除了当使用强大的命令时,这种情况发生在用户/程序员不需要意识到的级别。