我自学了小心使用划痕计数器。然而,我最近遇到了一个我无法理解的情况。考虑以下代码
\documentclass{article}
% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}
\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}
\makeatother
\begin{document}
\foo{\baz[2]}
\end{document}
而不是给出期望的输出
一 BlaBla
2 布拉布拉
三 BlaBla
四 BlaBla
第二行和第四行只有数字,但没有“BlaBla”。
我知道上面给出的代码至少存在两个(可能的)问题。
\relax
1)计数器赋值 后应有一个空格或\count@=#1
,从而阻止 TeX 扩展得太远。 (我确信 也存在类似的问题\ifnum
。)
2)临时寄存器\count@
可能比较危险,最好在团体内使用。
因此,解决方案应该是\baz
用
\newcommand*{\baz}[1][1]{\begingroup\count@=#1\relax\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}
这实际上给出了预期的结果。然而,令我困惑的是,
\newcommand*{\baz}[1][1]{\begingroup\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}
(分组但没有\relax
)和
\newcommand*{\baz}[1][1]{\count@=#1\relax\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}
(\relax
编辑但未分组)也有效!
我将非常感激任何帮助我了解正在发生的事情...
答案1
让我们看看是如何\@whilenum
定义的:
% latex.ltx, line 1089:
\long\def\@whilenum#1\do #2{\ifnum #1\relax #2\relax\@iwhilenum{#1\relax
#2\relax}\fi}
这第二呼叫\baz[2]
\count@=2\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\par
(其中 来自\par
的定义\foo
)因此变成
\count@=2\ifnum\count@>0\relax Bla\advance\count@\m@ne\relax
\@iwhilenum{\count@>0\relax Bla\advance\count@\m@ne\relax}\fi\par
但从第一次调用开始仍然是 0。请注意,尚未执行\count@
对的初始赋值,因为在对计数寄存器进行赋值时,TeX 会扩展标记以寻找更多数字:此处可扩展标记是。现在测试返回 false,所以我们剩下\count@
\ifnum
\ifnum
\count@=2\fi\par
并\fi
再次扩展以搜索更多数字。现在\par
不可扩展(假设它具有原始含义),因此执行了赋值并且没有打印任何内容,因为所有包含的标记Bla
都已被删除。但是,第三次调用\baz[2]
将以\count@
值 2 开始,这意味着测试\ifnum
返回 true。
您可以在以下文档上进行测试
\documentclass{article}
% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}
\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}}
\makeatother
\begin{document}
\makeatletter\the\count@ \count@=0 \makeatother
\foo{\baz[2]}
\end{document}
这表明了如何\count@
保存值 92,但在执行之前将其设置为 0\foo{\baz[2]}
会导致第一次调用为空白,而第二次调用会打印“BlaBla”。
\begingroup
这也是为什么用和包围代码\endgroup
似乎可以工作的原因,但同样,纯粹是运气好,的起始值\count@
是 92。如果是 0,行为会更糟:根本不会打印“Bla”。
\documentclass{article}
% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}
\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{%
\begingroup
\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}%
\endgroup
}
\makeatother
\begin{document}
\makeatletter\the\count@ \count@=0 \makeatother
\foo{\baz[2]}
\end{document}
原因始终相同:在尚未分配\ifnum
新值时执行测试,因此使用前一个值(92 或 0) 。\count@
\ifnum
删除\count@=0
后将产生四条“BlaBla”线。
当然,正确的代码是
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{%
\count@=#1 \@whilenum\count@>0\do{Bla\advance\count@\m@ne}%
}
或者用\relax
代替空格。空格将结束对进一步数字的搜索,并且将被 TeX 规则吞噬。
\relax
数字常数后始终留一个空格 (或)。
答案2
#1 后面缺少一个空格 (或\relax
):应该是\count@=#1 \@whilenum...
。由于缺少空格,tex\@whilenum
在尝试获取数字时会扩展 。
由于\@whilenum
使用\count@
这也意味着前一个循环的结束值会影响下一个循环。分组可以避免文档中的这种副作用。但这不是通用的解决方案。例如:
\documentclass{article}
% print the argument four times on numbered lines
\newcommand{\foo}[1]{1 #1\par 2 #1 \par 3 #1 \par 4 #1}
\makeatletter
% repeat "Bla" as many times as specified by the optional argument
\newcommand*{\baz}[1][1]{\begingroup\count@=#1\@whilenum\count@>0\do{Bla\advance\count@\m@ne}\endgroup}
\count@=0
\makeatother
\begin{document}
\foo{\baz[2]}
\end{document}