计数器、任务和组

计数器、任务和组

我自学了小心使用划痕计数器。然而,我最近遇到了一个我无法理解的情况。考虑以下代码

\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”。

我知道上面给出的代码至少存在两个(可能的)问题。

\relax1)计数器赋值 后应有一个空格或\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}

在此处输入图片描述

相关内容