在 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
,因为首先#1
foo 中的 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 能够与编程语言有所不同。如何交换两个符号的命令?,这表明并不总是需要使用中间变量。