\span
我曾多次碰到过这个原语。我感觉它是在 TeX 程序中定义的,甚至不在 中plain.tex
。对吗?那是什么做该命令的作用是什么?
答案1
\span
如果原语出现在序言中\halign
或正文中,则它有两种截然不同的含义。
如果它在前导码中,即在第一个之前,\cr
则表示“扩展”以下标记;如果它出现在正文中,则它会结束当前单元格,但会将其与下一个单元格合并。在这种情况下,它通常与 结合使用\omit
。
最简单的例子是
\halign{\hfil#\tabskip1em&#\hfil\cr
a&b\cr
c\span d\cr
}
第二行只有一个单元格。输出将是
“c” 和 “d” 之间没有\tabskip
间距,因为第二行只是一个单元格,其\hfil
前后有内容,但 TeX 会考虑\tabskip
到确定单元格的宽度。
然而,通常人们也会添加\omit
以去除你和五部分 (你指的是之前的内容#
,五以及其后的内容)。
当 TeX 读取 的前导码时,\halign
它不会扩展标记,除非它找到\tabskip
(当它扩展标记以找到适当的粘合规范时)。可以通过在标记前面加上 来强制扩展标记\span
。
一个例子是amsmath.sty
\def\align@#1#2{%
\inalign@true \intertext@ \Let@ \chardef\dspbrk@context\z@
\ifingather@\else\displ@y@\fi
\let\math@cr@@@\math@cr@@@align
\ifxxat@\else \let\tag\tag@in@align \fi
\let\label\label@in@display
#1% set st@r
\ifst@rred\else \global\@eqnswtrue \fi
\measure@{#2}%
\global\row@\z@
\tabskip\eqnshift@
\halign\bgroup
\span\align@preamble\crcr
#2%
}
这是一个用于align
和朋友的辅助宏;序言在宏中定义一次即可\align@preamble
\def\align@preamble{%
&\hfil
\strut@
\setboxz@h{\@lign$\m@th\displaystyle{##}$}%
\ifmeasuring@\savefieldlength@\fi
\set@field
\tabskip\z@skip
&\setboxz@h{\@lign$\m@th\displaystyle{{}##}$}%
\ifmeasuring@\savefieldlength@\fi
\set@field
\hfil
\tabskip\alignsep@
}
\align@
通过将大量代码放入宏中,这极大地简化了 的定义。它还允许align
通过修改来更改 的行为\align@preamble
(请参阅是否可以让奇数列隐式地带有前缀 {}?)。
LaTeX 内核对 使用了不同的方法tabular
,因为它需要根据其规则来构建前言,即lcrp
字符组合等等。
的第二种用法\span
是合并 中的列\halign
。在 Plain TeX 中,我们发现\multispan
\def\multispan#1{\omit \mscount#1\relax
\loop\ifnum\mscount>\@ne \sp@n\repeat}
\def\sp@n{\span\omit\advance\mscount\m@ne}
这样做并添加与参数中所述\omit
相同的对(减一);所以相当于,到等等。LaTeX的宏建立在同样的思想之上\span\omit
\multispan{1}
\omit
\multispan{2}
\omit\span\omit
\multicolumn
% latex.ltx, line 5053:
\long\def\multicolumn#1#2#3{\multispan{#1}\begingroup
\@mkpream{#2}%
\def\@sharp{#3}\set@typeset@protect
\let\@startpbox\@@startpbox\let\@endpbox\@@endpbox
\@arstrut \@preamble\hbox{}\endgroup\ignorespaces}
它首先执行此操作\multispan{#1}
(与 Plain 中的定义完全相同),然后继续构建“本地”对齐前导码,使用和评估其强制参数相同\@mkpream
的tabular
方法array
。
为什么 Knuth 会用相同的原语来表示两种截然不同的含义?为了节省空间;TeX 是在计算机内存空间不足时编写的,节省定义很重要。由于\span
只能出现在\halign
(或\valign
,当然),所以这不是问题。
第一个含义的不正当用法\span
出现在 TeXbook 练习 20.16 的解决方案中
下面的做法不必太当真,但确实有效:
{\setbox0=\vbox{\halign{#{\c\span\d}\cr \let\next=0\edef\next#1{\gdef\next{\b#1}}\next\cr}}} \let\a=\next
本练习是关于定义\a
等同于\b
(完全展开) 后跟\c
(未展开) 和\d
(仅展开一次) 而不使用\noexpand
和\the
。例如,如果我们有
\def\foo{xy\baz}
\def\baz{z}
\def\b{\foo\foo}
\def\c{--}
\def\d{\baz}
我们希望定义\a
其替换文本
xyzxyz\c\baz
该\setbox0=\vbox{
部分只是使用,\halign
没有任何输出。现在 TeX 将\halign
前导码评估为#{\c\baz}\cr
,因为\span
会导致一级扩展\d
。唯一的单元格包含
\let\next=0\edef\next#1{\gdef\next{\b#1}}\next
根据规则,它代替了#
前导码中的
\let\next=0\edef\next#1{\gdef\next{\b#1}}\next{\c\baz}\cr
只是\cr
结束了单元格,所以它不相关。由于\let\next=0
,\next
变得不可展开,因此\edef
导致\next
被定义为好像它已经
\def\next#1{\gdef\next{<full expansion of \b>#1}}
并将\next{\c\baz}
尽职尽责地执行
\gdef\next{<full expansion of \b>\c\baz}
最后\let\a=\next
一切都结束了。
这很好地解释\span
了一扩展的步骤。
使用 e-TeX 的另一种解决方案(毕竟它不使用\noexpand
和\the
)是
\edef\a{\b\unexpanded\expandafter{\expandafter\c\d}}