新答案

新答案

我经常看到用户使用以下方法重新定义宏(例如)

\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%
}

为什么使用\LetLtxMacrowhen\let也能达到同样的效果?或者更具体地说,何时使用\LetLtxMacroand/或为什么?

答案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软件包不支持它们。

相关内容