为了更好地理解/控制 \expandafter,我编写了这段代码。
\count100 = 0
\count101 = 1
\def\c{%
\the \count100
\advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}
% the ending dot-brace sequence remembers me the commands executed on each line
\expandafter\c\c\c \hfil $...$
\expandafter{\c\c}\c \hfil $\{..\}.$ % I think it's
% useless for
% this situation
\expandafter\c{\c}\c \hfil $.\{.\}.$
\expandafter\c{\c\c} \hfil $.\{..\}$
输出数字序列:
012
313
411
212
经过长时间思考这个结果并在互联网上查看后,我得出结论:这对我来说毫无意义。
有人能帮助我理解这样的输出吗?
答案1
基本上,您已将\everypar
计数寄存器 100 重置为值 0,并\c
传送命令\the\count100
,即计数寄存器 100 的当前值并增加该计数寄存器。
我认为棘手的事情不是\expandafter
各种情况的混合,
- 扩展不是排版,因此不会改变排版模式。
- 范围界定不是排版,因此不会改变排版模式。
\everypar
在某些时刻被调用,用于将标记“注入”/插入到标记流中。这些时刻是当 TeX 遇到触发切换到(不受限制的)水平模式的标记时 — (不受限制的)水平模式是排版模式,TeX 还会为您跨行拆分文本 — 并开始收集材料,收集完成后,将制作一个段落,其文本将尽可能整齐地跨行拆分。在那些时刻,在注入这些标记之前,将垂直\parskip
-glue 添加到垂直列表中,其中将附加形成段落行的框,并在包含\parindent
段落第一行的框的左端/开头插入水平 -indentation。(这是对事件的简化描述。)- TeX 切换到水平模式的时刻可能是之前打开本地范围和/或通过
\the
-expansion 或任何扩展技巧获取(尚未排版)标记和/或任何其他不会改变排版模式的事情发生的时刻。
\expandafter\c\c\c \hfil $...$
\count100
初始化为0
。TeX 处于垂直模式。我们有三个序列来传递 的值\count100
并然后增加\count100
:当处理\the\count100
来自第一个时\c
,您会得到标记0
。处理该标记会导致 TeX 对其进行排版并由此切换到水平模式并从而执行\everypar
,这反过来会重置\count100
为 0。然后执行待处理的\advance\count100 by\count101\relax
,同样来自第一个\c
,因此\count100
具有值 1。第二个和第三个\c
分别产生排版和增加 的值\count100
,以便您得到1
和2
并且最终\count100
具有值 3。
(只是产生在扩展第一个之前传递第二\expandafter
个的顶层扩展但这并不重要:第一个不使用属于第二个的顶层扩展的标记作为参数。)\c
\c
\c
\c
所以最后你得到了012
并且\count100
有值 3。当处理“0”时,TeX 切换到水平模式来排版一个段落,并在此将值为 0 的寄存器重置为值 0。当遇到空行时,段落就完成了,TeX 切换回垂直模式。
\expandafter{\c\c}\c \hfil $\{..\}.$ %
此处得出的结果是,在 TeX 看到花括号(从而打开局部作用域)之前,\expandafter
的顶层扩展被传递。在这种情况下,它不会对最终结果产生影响。无论如何,TeX 仍处于垂直模式并打开局部作用域。在该局部作用域中,来自第一个的标记传递/打印为 的当前值。当 TeX 处理该标记时,TeX 通过切换到水平模式来开始一个新段落。因此执行并将计数寄存器 100 重置为 0。后续的,也来自第一个,将计数寄存器 100 增加 1,因此现在它的值是 1。后续的/第二个打印 1 并将计数寄存器 100 增加 1,因此现在它的值是 2。结束括号关闭本地范围:现在计数寄存器 100 的值回到 3。第三个打印值“3”并增加寄存器,因此现在它的值是 4。后续的空行结束段落并且 TeX 返回垂直模式。\c
\the\count100
\c
3
\count100
\everypar
\advance \count100 by \count101\relax
\c
\c
\c
\expandafter\c{\c}\c \hfil $.\{.\}.$
这里\expandafter
导致 TeX{
在查看第一个之前先查看 (不可扩展)\c
,因此\expandafter
没有效果。
第一个\c
传递:
\the \count100 \advance \count100 by \count101\relax
。
由于计数寄存器 100 的值为 4 \the\count100
,因此第一个\c
传递4
。为了排版,此标记 TeX 切换到水平模式,从而\everypar
执行 ,以便\count100
将 重置为 0。后续的\advance\count100 by \count101\relax
,也来自第一个\c
,递增\count100
,以便现在的值为 1。
现在打开局部作用域。在局部作用域内,由于第二个,传递 的\c
当前值\count100
,即 ,1
并且\count100
递增,现在的值为 2。然后关闭局部作用域,并将 的值\count100
重置为打开局部作用域之前的值,即1
。第三个\c
传递该值的排版1
并递增,\count100
以便\count100
现在的值为 2。
后续的空行导致 TeX 完成该段落并切换回垂直模式。
\expandafter\c{\c\c} \hfil $..\{.\}$
再次\expandafter
导致 TeX{
在查看第一个之前先查看 (不可扩展)\c
,因此\expandafter
没有效果。
第一个命令\c
导致 TeX 传递\the \count100 \advance \count100 by \count101\relax
。由于计数寄存器 100 的值为 2,\the\count100
因此产生令牌2
。处理此令牌时,TeX 切换到水平模式并执行\everypar
。因此计数寄存器被重置为值0
。然后执行后续命令\advance \count100 by \count101\relax
,产生计数寄存器 100 的值为 1。然后通过 打开本地作用域{
。然后,由于第二个命令\c
,传递计数寄存器 100 的值,即令牌,1
并递增计数寄存器 100,使其值为“2”。然后,由于第三个命令\c
,传递计数寄存器 100 的值,即令牌,2
并递增计数寄存器 100,使其值为“3”。然后通过 关闭本地作用域}
,计数寄存器 100 的值回到 1。
答案2
首先要意识到的是,与 相关的重置\everypar
只有在您离开垂直模式时才会生效。因此,为了使事情变得简单,让我们修改\c
定义以最初离开垂直模式。其他一切保持不变。
要意识到的第二件事是,每个命令\expandafter
都没有完成任何事情(稍后会解释),因此为了理解输出,可以忽略它们。
\documentclass{article}
\begin{document}
\count100 = 0
\count101 = 1
\def\c{\leavevmode%
\the \count100
\advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}
% the ending dot-brace sequence remembers me the commands executed on each line
\expandafter\c\c\c \hfil $...$
\expandafter{\c\c}\c \hfil $\{..\}.$ % I think it's
% useless for
% this situation
\expandafter\c{\c}\c \hfil $.\{.\}.$
\expandafter\c{\c\c} \hfil $..\{.\}$
\end{document}
使用此设置,每个段落中第一次调用的\c
都将以 等于 开始\count100
。0
在第一种情况下,\c\c\c
,其行为符合预期,初始重置为0
,每次调用时增量为 1。
第二种情况{\c\c}\c
是将 重置为0
组内,从而占01
输出中的首字母。但是,当组结束时,重置被限制在组内,因此第三种\c
情况从上一行剩下的内容开始递增,即递增2
为3
。
第三行\c{\c}\c
具有类似的行为。计数被重置为0
,它1
在进入组之前递增为 。但是,在退出组时,它只调用 的组前值1
,因此最后\c
再次打印1
,然后再递增。
在最后一个示例中,调用产生了。但是,在退出组时,即第三次调用之后,012
的值仍然是,这是计数器在进入组之前递增的值。\count100
1
现在让我们来理解为什么 什么\expandafter
都没做。作用于 时\c
,它只是用 的定义替换了\c
标记\c
。但是,\the\count100
仍然没有执行(甚至 也没有执行\leavevmode
)。因此,它对 内部任何内容的预扩展没有影响\c
。在某些情况下,您甚至没有扩展\c
,而是试图扩展{
,这什么也没做。
最后,让我们回到 OP 的原始案例,其中省略了\leavevmode
(\expandafter
仍然无用)。现在,与 关联的计数重置\everypar
直到离开垂直模式才会执行,而这直到\the\count100
在第一次调用 时遇到初始值时才会发生\c
。
因此,第一行按预期运行,只是因为\count100
最初等于0
。
对于第二行,上一段末尾的 left 值\count100
有效,\the\count100
并且然后重置为 0。因此,31
第二行的 反映了这一点。但是,与之前一样,当组结束时,最后的\c
调用只会记住3
前一段末尾的 。
对于第三行,4
反映了前一段末尾的值,在排版0
后重置为 。因此,第二行产生了。然而,最后一行也产生了,因为组内计数的增量在范围内丢失了。4
\c
1
\c
1
类似地,对于最后一段,首字母2
是从前一段的末尾留下的,并且然后计数被重置为0
。因此,第 2 次和第 3 次调用产生12
。
如果我明白了您想要通过\expandafter
s 实现什么目标,我也许能够提供帮助,但我不清楚您希望实现什么目标。
答案3
首先,也是最重要的:你\expandafter
什么都没做。
如果你希望它在重新检查\count100
第一条指令之前导致被执行,那么你的期望是错误的。事实上,在扩展之后,TeX 将看到标记列表(换行只是为了方便阅读)\c
\expandafter
\c
\the\count100 \advance\count100 by \count101\relax
\c
它会\c
根据其定义继续扩展。所以你会得到
\the\count100 \advance\count100 by \count101\relax
\the\count100 \advance\count100 by \count101\relax
\c
所以你得到
- 打印0;
\count100
步进至 1;- 1 已打印;
\count100
步进至 2;- 2 已打印;
\count100
已升至 3。
最后,\hfil$..$
被处理。现在来\par
了结束段落。现在你有
\expandafter{\c\c}\c \hfil text
(后面的内容\hfil
基本上无关紧要)。同样,在这种情况下,\expandafter
TeX 最终看到
{\the\count100 \advance\count100 by \count101\relax\c}\c \hfil text
OK,打开一个组,TeX 展开\the
并意识到这会导致处理一个字符标记。这个字符标记是,也就是此刻3
的值。因为这将\count100
开始一个段落,在提供缩进框后,传递的内容\everypar
;计数器重置为 0,但\the\count100
已经展开,因此结果是
\count100
重置为0;- 3 已打印;
\count100
步进至 1;- 1 已打印;
\count100
步进至 2;- 该组结束并
\count100
取回其在组开始之前的值 - 3 已打印;
\count100
已升至 4。
现在你可以继续解释下一个输出,我已经证明了 012 和 313做有道理。可能不是你所期望的,但是……
\everypar
用来后某件事引发了一个段落的开始;\expandafter
导致只是一扩展步骤;- 无论如何,在宏扩展期间不会执行分配。
第 1 点非常重要。你可以尝试解释一下你得到了什么
\count100 = 0
\count101 = 1
\def\c{%
\the \count100
\advance \count100 by \count101\relax}%
%
\everypar{\count100 = 0\relax}
% the ending dot-brace sequence remembers me the commands executed on each line
\noindent\c\c\c \quad $...$
\noindent{\c\c}\c \quad $\{..\}.$
\noindent\c{\c}\c \quad $.\{.\}.$
\noindent\c{\c\c} \quad $..\{.\}$
我删除了\expandafter
令牌,因为它们没有任何意义;\noindent
会导致\everypar
令牌被传递并\quad
产生更清晰的输出。