我经常看到用户使用以下方法重新定义宏(例如)
\let\oldmacro\macro% Store \macro in \oldmacro
\renewcommand{\macro}{%
% ...redefined \macro
\oldmacro%
}
其他时候他们使用
\usepackage{letltxmacro}% http://ctan.org/pkg/letltxmacro
\LetLtxMacro{\oldmacro}{\macro}% Store \macro in \oldmacro
\renewcommand{\macro}{%
% ...redefined \macro
\oldmacro%
}
为什么使用\LetLtxMacro
when\let
也能达到同样的效果?或者更具体地说,何时使用\LetLtxMacro
and/或为什么?
答案1
新答案
2020 年 6 月之后的 LaTeX 内核将提供更好的
\NewCommandCopy
而且还有\RenewCommandCopy
和\DeclareCommandCopy
。第一个执行的工作与“原始答案”中描述的工作相同,但也可以与通过(或兄弟)\LetLtxMacro
定义的命令一起使用。\NewDocumentCommand
与 不同\LetLtxMacro
,如果我们想要的别名已经定义,它将引发错误。在这种情况下,可以使用\RenewCommandCopy
(如果别名是不是定义)或\DeclareCommandCopy
(不进行检查)。使用这两种形式时要小心。
请优先选择\NewCommandCopy
,\let
除非您确切知道这样\let
可以。
原始答案
主要有两种情况\LetLtxMacro
需要使用(在其他情况下使用也不会有害)。
情况1
获取副本的命令已定义为\DeclareRobustCommand
,例如
\DeclareRobustCommand{\foo}[1]{-#1-}
(和任何参数个数,甚至是零)。还有其他以不同方式定义的命令,但最终都属于同一情况。
这里发生的事情是 LaTeX 定义二命令,类似于
\def\foo{\protect\foo•}
\newcommand\foo•[1]{-#1-}
我的•
意思是空间texdef
在第二个命令的名称中。如何实现这个技巧并不重要。我们可以通过在命令行中询问来判断命令是否属于此类:
> texdef -t latex texttt
我们得到
\texttt:
macro:->\protect \texttt
\texttt :
\long macro:#1->\ifmmode \nfss@text {\ttfamily #1}\else \hmode@bgroup \text@command {#1}\ttfamily \check@icl #1\check@icr \expandafter \egroup \fi
这准确地显示了正在发生的事情:texdef
首先打印的含义\texttt
,即\protect\texttt•
(这里的空格是不可见的),但随后了解此命令的性质,因此还打印的含义\texttt•
,这通过冒号前的空格显示出来。
这样做会有什么问题
\let\oldfoo\foo
\renewcommand\foo[1]{!\oldfoo{#1}!}
用于改变 的工作方式\foo
?让我们看看会发生什么(我将继续使用•
来表示宏名称中的空格)。
\foo{BAR}
!\oldfoo{BAR}!
!\protect\foo•{BAR}!
!-BAR-!
这看起来很不错:更新后的宏可以满足我们的要求。但是等一下!我们使用\DeclareRobustCommand
以确保\foo
在移动参数中正确处理。那么让我们看看当在移动参数\foo{BAR}
(例如节标题)中使用时会发生什么;文件中的注释.toc
将包含
!\foo {BAR}!
最终将导致打印!!-BAR-!!
,也就是不是这正是我们想要的。为什么?因为书写过程会扩展未受保护的宏。因此 LaTeX 不会
\foo{BAR}
!\oldfoo{BAR}!
!\protect\foo•{BAR}!
!\foo• {BAR}!
由于\foo•
前面有\protect
,所以写入的是最后一行。但是当.toc
读入文件时,TeX 只会看到 后面的空格\foo
,因为它输入的是文本文件,所以它会顺理成章地扩展\foo
。
如果使用
\LetLtxMacro{\oldfoo}{\foo}
LaTeX 基本上可以做到
\def\oldfoo{\protect\oldfoo•}
\let\oldfoo•\foo•
这样上面的问题就不会再出现了。
案例 2
如果我们想要保存的命令的含义已被定义为\newcommand
具有可选参数,那么就会存在与前一种情况类似的风险;它们在这个答案. 情况如下
\newcommand{\xyz}[2][!]{#1-#2}
例如,\smash
(由 重新定义amsmath
)就属于这种情况。
texdef -t latex -p amsmath smash
我们会得到
\smash:
macro:->\@protected@testopt \smash \\smash {tb}
这是一个明显的标志,\LetLtxMacro
应该加以利用。
其他案例
\newrobustcmd
如果已经从包中定义了保存命令etoolbox
,\LetLtxMacro
则应使用它来安全地复制其含义。
警告
不要尝试\LetLtxMacro
使用 定义的命令\NewDocumentCommand
(或类似命令)xparse
。该letltxmacro
软件包不支持它们。