对于编写我自己的 TeX 代码,分组(\begingroup
,\endgroup
)有助于自动保存和恢复 TeX“变量”。
(La)TeX“变量”可以是宏或寄存器。寄存器可以是计数器(整数)、维度、跳过(带单位的小数)或读/写处理程序等。请注意,您可以在组内更改它们,即
{
..}
或\begingroup
...\endgroup
或 LaTeX 环境,然后它们的旧值会在组末尾自动恢复。(Martin Scharrer,如何保存变量)
另一方面,分组是不可扩展的(因为原语\begingroup
和\endgroup
是不可扩展的)。
TeX 如何处理分组?TeX 是否仅在对特定变量进行赋值时才保存和恢复该变量?似乎当发生全局赋值(例如字体赋值:
\fontdimen
、、;连字符赋值:、;框大小赋值:、、;...)时,TeX 会省略保存和恢复。\hyphenchar
\skewchar
\hyphenation
\patterns
\ht
\dp
\wd
(编辑) 考虑一个使用 5 到 10 个“变量”的 TeX 命令。换句话说,它暂时为它们分配不同的值,例如
\baselineskip=30pt
。在这种情况下有什么好的理由避免分组吗?或者由于上述优势,应该在这里使用分组?
答案1
只是想补充一些其他答案并尝试回答问题的标题:
何时使用或避免分组
正如 David 提到的,这主要取决于您的使用情况,但以下是建议使用分组的一些典型领域:
在列表中设置时
\@elt
。\def\alist{\@elt a\@elt b\@elt c} \begingroup \let \@elt\@gobble \alist \endgroup \begingroup \def\@elt#1{-#1- } \alist \endgroup
定义影响文本段落的宏时。
\def\startlines{% \begingroup \obeylines\obeyspaces \leavevmode } \def\endlines{\endgroup} \startlines This is a test ... ...... ....... test and another test This is a test ... ...... ....... test and another test \endlines
当你想将一个命令拆分成两部分时,例如:
\def\index{\@bsphack\begingroup \@sanitize\@index} \def\@index#1{\endgroup\@esphack}
更改 catcode 时:
\def\MyCatcodeMagic#1{% \begingroup \catcode`\ 10 % \ifnum \endlinechar<256 % ... \endgroup }
在数学命令中避免“泄露”样式变化
何时避免分组主要取决于编程风格。良好的编程习惯表明分组globals
是一种弊端,然而,使用 TeX/LaTeX 很难做到这一点。LaTeXsource2e
倾向于对大多数重要的宏使用分组,即使在以下情况下也是如此:
\def\frac#1#2{{\begingroup#1\endgroup\over#2}}
你能猜出为什么吗?
答案2
有些作业不尊重分组:
\hyphenation{<words>}
\patterns{<balanced tokens>}
\batchmode|\nonstopmode|\scrollmode|\errorstopmode|\interactionmode=<2 bit number
\hyphenchar<font>=<number>
\skewchar<font>=<number>
\spacefactor|\prevgraf|\deadcycles|\insertpenalties=<number>
盒子维度设置比较特殊。当 TeX 作用于盒子寄存器时,它确实会考虑分组,但会考虑盒子的最后一个化身。例如:
\setbox0=\hbox{\hskip 10pt}{\wd0=0pt}
会导致框 0 的宽度为 0pt,即使该分配是针对组执行的。相反,
\setbox0=\hbox{\hskip 10pt}{\setbox0=\hbox{def}\wd0=0pt}
将导致盒子寄存器 0 具有宽度 10pt,因为它很容易验证。
这与使用盒子寄存器时发生的情况类似:
\setbox0=\hbox{abc}{\box0}
会导致打印abc
,并且框寄存器 0 在关闭组后也为空。使用
\setbox0=\hbox{abc}{\setbox0=\hbox{def}\box0}
\hbox{abc}
关闭组后,将打印“def”并且框寄存器 0 将包含。
另一个特殊情况是字体加载:如果有人说
{\font\foo=somefont}
那么\foo
在组外就无法访问,但是字体信息已经存储在字体内存中并且不会被释放。
TeX 在堆栈中存储什么值?只有那些在组内更改的值。是进行本地分配时堆栈耗尽的风险,但现代 TeX 实现保留了至少 50,000 个字的堆栈内存,这对于大多数应用程序来说已经足够了,只要编程良好,即相同的“变量”始终在本地或全局上起作用(因此保存堆栈不会保留它无法释放的值)。
LaTeX 中的每个环境都形成一个组,但环境除外document
:这样做是荒谬的每一个在组内的文档中分配适当的内容,对保存堆栈产生不良影响。但是,除了非常特殊的应用程序外,很难想象会耗尽保存大小。
在许多情况下,使用组更容易,因为它不需要存储最后放回的值。至于可扩展性,不{
赋值是可扩展的,因此和}
或\begingroup
和不可扩展这一事实\endgroup
对于讨论完全无关。
因此,如果您必须完全更改\lccode
整个 Unicode 范围的向量(在 XeTeX 或 LuaTeX 中),那么不要在组中执行此操作,因为您将执行 2^21 次分配。如果您必须将某些内容保留在内存中直到处理结束,那么请使用全局分配,这些分配将在组结构中保留下来,并且不会污染保存堆栈。如果您想更改列表环境的边距,请使用已经提供的组结构。
答案3
对于任何非全局赋值,TeX 都会将旧值保存在“保存堆栈”中,并在当前组的末尾恢复它。您可以在日志文件中看到使用了多少保存堆栈
23i,4n,21p,148b,149s stack positions out of 5000i,500n,10000p,200000b,50000s
^^^ ^^^^^^
你的第二个问题不清楚,没有赋值是可扩展的\let
\def
,寄存器赋值都是不可扩展的操作
关于是否使用分组的问题实际上无法回答,一般来说,是否使用组主要取决于您是否要使用组,而不是取决于进行哪些分配的细节。
在
\begin{empty}
....
\end{empty}
对于环境来说,\empty
其实现方式是扩展为无,所以不进行任何分配,但环境是一个组这一事实意味着进入的任何用户代码都...
受该组范围的限制。
有一次我需要使用全局赋值,并在宏层的私有实现堆栈中明确保存和恢复值,而不是使用 TeX 分组,这是在blockarray
语法
\begin{block}
....
\end{block}
但实际上,开始和结束代码位于不同的表格单元格中,因此在不同的组中,与实际环境不同,没有从块的开头开始并在结尾结束的组。代码中的注释说
\subsection{`Local' Variables}
Most of {\tt blockarray} happens inside a "\halign" which means that
the different parts have to make global assignments if they need to
communicate. However many of these assignments are logically local to
{\tt blockarray}, or a sub-environment like {\tt block}. This means
that I have to manage the saving and restoring of local values `by
hand'.
Three different mechanisms occurred to me, I have used all of them in
this style, mainly just to try them out!
您可以查看blockarray
包文档以了解详细信息。
答案4
与 Java、Python 和其他编程语言不同,在 TeX 中,受分组影响的基本编程结构不是多变的, 但任务,其中有很多种。有些赋值将值赋给变量,即控制序列,但其他赋值将值赋给内部量。并非所有赋值都遵守 TeX 的分组。我将用这个答案来完整描述 TeX 赋值。这些描述摘自 TeXbook(第 20 次印刷,Addison-Wesley 1991),页码引用自该书。
所有 TeX 赋值在所有模式下都具有相同的效果。赋值命令通常包含一个=
符号,但在所有这些情况下,该符号都是可选的。(第 275 页)
给定的分配可以是全局的,也可以是局部的。如果分配以 为前缀,\global
或者它是下面全局分配列表中的分配之一,则分配是全局的。否则,分配是局部的。
局部赋值遵循 TeX 的分组结构;即,当前组结束时,更改的数量将恢复为以前的值。另一方面,全局赋值的影响会跨越分组边界持续存在。(第 277 页)
每个分配都可以以 (p. 275) 作为前缀,但前缀\global
的存在与否对下面全局分配列表中列出的分配没有影响。 (p. 277)命令相当于,并且相当于。 (p. 276)\global
\gdef
\global\def
\xdef
\global\edef
如果\globaldefs
在赋值时参数为正,则\global
自动隐含前缀 ;但如果\globaldefs
在赋值时为负,则忽略前缀 \global 。如果\globaldefs
为零(通常为零), 的出现或不出现\global
决定是否进行全局赋值。(第 275 页)
值得注意的与分组无关的赋值怪癖是,TeX 采取预防措施来确保像 ' \chardef\cs=10\cs
' 和 ' \font\cs=name\cs
' 这样的构造,即在定义控制序列后立即使用的赋值,\cs
在赋值完成之前不会扩展第二个。(第 278 页)这与 TeX 的正常扩展规则相反,该规则保证例如\after
中的控制序列
\def\after{0}%
\chardef\cs=1\after
将在定义期间扩展\cs
,因此最终效果将与
\def\after{0}%
\chardef\cs=10
全球任务
以下是 TeX 所有固有全局分配的详尽列表。(第 277-278 页和第 271 页)这些分配的效果超越了分组。
- 字体分配
\fontdimen ... = ...
\hyphenchar ... = ...
\skewchar ... = ...
- 连字分配
\hyphenation ...
\patterns ...
- 盒子尺寸分配
\ht<8-bit number> = ...
\wd<8-bit number> = ...
\dp<8-bit number> = ...
- 交互模式分配
\errorstopmode
\scrollmode
\nonstopmode
\batchmode
- 亲密任务
- 特殊整数赋值
*\spacefactor = ...
*\prevgraf = ...
*\deadcycles = ...
*\insertpenalties = ...
- 特殊尺寸分配
*\prevdepth = ...
*\pagegoal = ...
*\pagetotal = ...
*\pagestretch = ...
*\pagefilstretch = ...
*\pagefillstretch = ...
*\pagefilllstretch = ...
*\pageshrink = ...
*\pagedepth = ...
- 特殊整数赋值
本地任务
以下是 TeX 所有本地分配的详尽列表。(第 275-277 页和第 271 页)除非前面有\global
,否则这些分配的效果仅限于当前(最内部)分组。
宏分配
\def ...
\edef ...
非宏分配
- 变量赋值
- 整数赋值
*<integer parameter> = ...
(请参阅下面的整数参数完整列表)
*<countdef token> = ...
(<countdef token>
= 由 定义的 c-seq\countdef
)
*\count<8-bit number> = ...
- dimen 分配
*<dimen parameter> = ...
(请参阅下面的 dimen 参数完整列表)
*<dimendef token> = ...
(<dimendef token>
= 由 定义的 c-seq\dimendef
)
*\dimen<8-bit number> = ...
- 粘合分配
*<glue parameter> = ...
(请参阅下面的粘合参数完整列表)
*<skipdef token> = ...
(<skipdef token>
= 由 定义的 c-seq\skipdef
)
*\skip<8-bit number> = ...
- muglue 分配
*<muglue parameter> = ...
(请参阅下面的 muglue 参数完整列表)
*<muskipdef token> = ...
(<muskipdef token>
= a c-seq. def. by\muskipdef
)
*\muskip<8-bit number> = ...
- 标记分配
*<token parameter> = ...
(请参阅下面的标记参数完整列表)
*<toksdef token> = ...
(<toksdef token>
= 由 定义的 c-seq\toksdef
)
*\toks<8-bit number> = ...
- 整数赋值
- 算术
\advance ... (by) ...
\multiply ... (by) ...
\divide ... (by) ...
- 代码分配
\catcode<8-bit number> = ...
\mathcode<8-bit number> = ...
\lccode<8-bit number> = ...
\uccode<8-bit number> = ...
\sfcode<8-bit number> = ...
\delcode<8-bit number> = ...
- let 赋值
\futurelet ...
\let ... = ...
- 简写定义
\chardef ... = ...
\mathchardef ... = ...
\countdef ... = ...
\dimendef ... = ...
\skipdef ... = ...
\muskipdef ... = ...
\toksdef ... = ...
- fontdef 标记分配
<fontdef token>
(<fontdef token>
= 定义的控制序列\font
)\nullfont
- 家庭任务
\textfont<4-bit number> = ...
\scriptfont<4-bit number> = ...
\scriptscriptfont<4-bit number> = ...
\parshape = ...
\read ... to ...
\font ... = ... (at/scaled ...)
\setbox<8-bit number> = ...
TeX 的 103 个参数
以下是 TeX 所有参数的详尽列表。
整数参数(55)
\adjdemerits
\binoppenalty
\brokenpenalty
\clubpenalty
\day
\defaulthyphenchar
\defaultskewchar
\delimiterfactor
\displaywidowpenalty
\doublehyphendemerits
\endlinechar
\errorcontextlines
\escapechar
\exhyphenpenalty
\fam
\finalhyphendemerits
\floatingpenalty
\globaldefs
\hangafter
\hbadness
\holdinginserts
\hyphenpenalty
\interlinepenalty
\language
\lefthyphenmin
\linepenalty
\looseness
\mag
\maxdeadcycles
\month
\newlinechar
\outputpenalty
\pausing
\postdisplaypenalty
\predisplaypenalty
\pretolerance
\relpenalty
\righthyphenmin
\showboxbreadth
\showboxdepth
\time
\tolerance
\tracingcommands
\tracinglostchars
\tracingmacros
\tracingonline
\tracingoutput
\tracingpages
\tracingparagraphs
\tracingrestores
\tracingstats
\uchyph
\vbadness
\widowpenalty
\year
(第 272-273 页)
尺寸参数 (21)
\boxmaxdepth
\delimitershortfall
\displayindent
\displaywidth
\emergencystretch
\hangindent
\hfuzz
\hoffset
\hsize
\lineskiplimit
\mathsurround
\maxdepth
\nulldelimiterspace
\overfullrule
\parindent
\predisplaysize
\scriptspace
\splitmaxdepth
\vfuzz
\voffset
\vsize
(第 274 页)
胶水参数 (15)
\abovedisplayshortskip
\abovedisplayskip
\baselineskip
\belowdisplayshortskip
\belowdisplayskip
\leftskip
\lineskip
\parfillskip
\parskip
\rightskip
\spaceskip
\splittopskip
\tabskip
\topskip
\xspaceskip
(第 274 页)
Muglue 参数 (3)
\medmuskip
\thickmuskip
\thinmuskip
(第 274 页)
Token 参数(9)
\errhelp
\everycr
\everydisplay
\everyhbox
\everyjob
\everymath
\everypar
\everyvbox
\output
(第 275 页)
{
* TeX 自动在分配给参数的参数开头插入一个开始组符号 ' ',并}
在其结尾插入一个结束组符号 ' ' \output
。(第 253 页)
原始分组环境
以下 ' {
' 代表 catcode 为 1 的字符标记或\let
此类字符标记的控制序列。类似地,' }
' 代表 catcode 为 2 的字符标记或\let
此类字符标记的控制序列/活动字符。
以下是除明显的\begingroup ... \endgroup
和纯粹的之外的所有原始分组环境的综合列表{...}
。
\halign<box specification>{<alignment material>}
\valign<box specification>{<alignment material>}
\noalign<filler>{<vertical mode material>}
使用
\halign
,TeX 分别\valign
进入一个新的分组级别,用 和 表示{
,}
对 的更改\tabskip
将被限制在其中。对齐材料还可以\noalign<filler>{<vertical mode material>}
在行之间包含可选的 ;这增加了另一个分组级别。当 TeX 处理对齐的每个单独条目时,它也会进入额外的分组级别。(第 282 页,第 285-286 页)\insert<8-bit number><filler>{<vertical mode material>}
\vadjust<filler>{<vertical mode material>}
'
{
' 使 TEX 进入以匹配的 ' ' 结尾的新分组级别}
。(第 280-281 页)\hbox<box specification>{<box material>}
\vbox<box specification>{<box material>}
\vtop<box specification>{<box material>}
\vcenter<box specification>{<box material>}
'
{
' 启动一个新的分组级别,并以匹配的 '}
' 结束。(第 278、290 页)$<math mode material>$
$$<math mode material>$$
在水平模式下遇到‘
$
’或‘$$
’时,TeX 进入新的分组级别,并停留在那里直到遇到匹配的‘$
’或‘$$
’。(第 287、293 页)在数学模式中遇到的一
{...}
对(与其他模式一样)表示新的分组级别。(第 290、291 页)\eqno<math mode material>$
\leqno<math mode material>$
\eqno
分别读取时\leqno
,TeX 进入新的分组级别,直到数学列表的末尾(以结束的“$
”表示)。(第 293 页)\left<delim><math mode material>\right<delim>
TeX使用 '
\left
' 开始一个新组,并以 ' ' 终止\right
。(第 292 页)