\x@protect 的用途是什么以及它如何工作?

\x@protect 的用途是什么以及它如何工作?

LaTeX 中有几个命令已经变得更加强大,因此它们可以在其他命令的参数中使用,从而移动它们的内容(问题的解释在脆弱命令和坚固命令之间有什么区别?)。LaTeX 通常只为此定义两个命令,一个调用\protected 辅助命令,另一个执行实际操作。作为示例,让我们看一下 的定义\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或)。请注意,在写入文件时,\stringTeX 不会在后面添加空格。\(

为什么?

正如开头所说,我们不希望空格在控制符号(例如\(\@(或活动字符))之后悄悄出现。如果遵循与控制字相同的路径,结果将是\(•不希望的书写。

移动参数和 \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不应该在任何地方使用:它没有任何用处除了当使用强大的命令时,这种情况发生在用户/程序员不需要意识到的级别。

相关内容