具有副作用的宏参数的多重评估?

具有副作用的宏参数的多重评估?

在 C 语言中,预处理器宏可能会产生意想不到的效果,因为它们的参数可以被多次求值。例如,以下代码:

#define MAX(a,b) ((a)>(b) ? (a) : (b))
int i = 5, j = MAX(i++, 0);

变成:

int i = 5, j = ((i++)>(0) ? (i++) : (0));

并且变量的i值为 7 — 而不是预期的 6 — 因为宏的参数在宏定义中重复。

LaTeX 有类似的问题吗?例如,以下情况会发生什么:

\newcommand{\foo}[1]{#1 #1}
\foo{\somethingwithsideeffects}

副作用会发生两次吗?如果会,如何避免?可以\edef巧妙地使用以使副作用仅发生一次吗?或者\sbox(假设参数是您想要装箱的东西)?或者也许有某种方法可以自动从中删除具有副作用的标记#1

这在实践中是一个问题吗?

答案1

TeX 中也会出现这种副作用。据我所知,没有真正的方法可以避免它们。也就是说,如果你制作了与示例中类似的内容,则无法进行更改\foo以适应这种情况。请看以下示例:

\documentclass{article}
\begin{document}
\gdef\switch{1}
\def\temp{\ifnum\switch=1\def\switch{0}x\else\def\switch{1}y\fi}
\def\foo#1{#1 #1}
\foo\temp
\end{document}

这将产生x y,因为首先#1foo 中的 s 被替换为它们的替换文本 ( \temp),然后逐个展开它们。这意味着在展开\switch第二个之前会发生变化\temp。在这个简单的例子中,分组会有所帮助。如果我们将 的定义更改为 ,\foo那么\def\foo#1{{#1}{#1}}它将打印。但是,当我们在 中而不是 时x x,这会停止工作。问题是,如果我们无法修改,那么我们实际上无法在 中对此做任何事情。因此,如果您将具有副作用的宏传递给多次使用该参数的宏,则可能会导致问题。\gdef\def \switch\temp\temp\foo

答案2

这是处理浮动字幕时常见的问题。例如,下面是\@makecaption其定义\caption(取自article.cls):

\long\def\@makecaption#1#2{%
  \vskip\abovecaptionskip
  \sbox\@tempboxa{#1: #2}%
  \ifdim \wd\@tempboxa >\hsize
    #1: #2\par
  \else
    \global \@minipagefalse
    \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
  \fi
  \vskip\belowcaptionskip}

标题设置在框内\@tempboxa,然后测试其水平宽度。如果合适,则\@tempboxa设置原始框,否则重新评估。重新评估是为了将标题从居中(默认)切换到段落样式对齐。因此,\label应谨慎使用任何或计数器相关条目(特别是如果还创建了浮动内容)。这是一个小例子:

在此处输入图片描述

\documentclass{article}
\newcounter{myitem}
\newcommand{\mynewitem}{\addtocounter{myitem}{1}I-\themyitem}
\begin{document}
\begin{figure}[ht]
  \centering\rule{150pt}{\baselineskip}
  \caption{This is \mynewitem.}
\end{figure}
\begin{figure}[ht]
  \centering\rule{150pt}{\baselineskip}
  \caption{This is \mynewitem\ together with a lengthy caption that stretches more than one line.}
\end{figure}
\end{document}

这些问题的解决方案有时由手动解决方法提供(可能因具体情况而异),也可能是一个软件包。大多数解决方案都是通过追溯源头并尝试了解问题的根源提供的,这总是会为可能的解决方法提供提示。

附言一下。David 的回答中有一个有趣的例子,说明了 TeX 能够与编程语言有所不同。如何交换两个符号的命令?,这表明并不总是需要使用中间变量。

相关内容