我正在尝试定义自己的自定义环境,但遇到了这个问题:当我在环境的启动代码中设置一个宏时,该宏会在环境结束时失去其值。
更准确地说,使用以下 LaTeX 代码
\def\foo{init}
\newenvironment{test}{
entering test environment, foo = \foo\\
\def\foo{bar}
updated to foo = \foo\\
}{
exiting test environment, foo = \foo\\
}
\begin{test}
inside environment: foo = \foo\\
\end{test}
outside environment: foo = \foo\\
\begin{test}
inside environment: foo = \foo\\
\end{test}
outside environment: foo = \foo\\
我得到以下输出:
进入测试环境,foo = init
更新为 foo = bar
环境内部:foo = bar
退出测试环境,foo = bar
环境外部:foo = init
进入测试环境,foo = init
更新为 foo = bar
环境内部:foo = bar
退出测试环境,foo = bar
环境外部:foo = init
如您所见,宏的值似乎在环境的结束代码结束时重置。这提出了两个问题:
这里发生了什么?
这是某种局部作用域,其中局部宏会遮盖全局宏的值吗?还是我还未理解的其他机制?我如何才能以某种方式为环境内的宏分配一个值,以便之后可以实际使用它?
答案1
当你使用以下方式定义环境时
\newenvironment{envname}{begincode}{endcode}
并使用它
\begin{envname}
body
\end{envname}
LaTeX 大致执行
\begingroup
begincode % as \envname
body
endcode % as \endenvname
\endgroup
这意味着在 内发生的所有分配begincode
都是endcode
在bodycode
环境中隐含的组本地进行的。
在您的情况下,\def\foo{bar}
then 仅适用于环境 - 在环境之外,原始值将恢复。您可以全力以赴\gdef
并进行全局分配:
\gdef\foo{bar}
但是,的效果\gdef
确实是全球性的,并且在有多个群体的复杂情况下,其效果可能与环境“之上”的一个范围的局部分配不同。
一些作业(例如 LaTeX 计数器操作)始终是全局的,不需要额外的工作即可摆脱环境的限制,但大多数其他作业都是本地的,需要类似的东西\gdef
才能在环境之外应用。
请注意,使用\def
(具有全局伴随项\gdef
) 使我可以轻松完成此操作。例如,LaTeX\newcommand
和\renewcommand
没有全局伴随项,必须用\gdef
构造替换。
答案2
LaTeX 在进入环境时打开一个组,在退出时关闭它,这是有充分理由的。
第一个原因。
当前环境的名称保存在宏中\@currenvir
;当\end
命令发生时,LaTeX 可以检查其参数与的含义\@currenvir
;如果一致,则为好,否则会引发错误。然后可以关闭该组并恢复执行\@currenvir
时的含义。\begin
另一种方法是维护一个堆栈,每次\begin
将一个项目推入堆栈并\end
弹出一个项目。但由于分组还有其他用途,因此在 LaTeX 诞生时(计算机内存稀缺),这样做毫无意义。
第二个原因
有几种环境用于特殊排版:例如center
,,,等等。flushleft
enumerate
itemize
这些环境中的每一个都可能设置很多参数,在这里我们看到维护堆栈会变得很痛苦;只要想想enumerate
嵌套在中itemize
。更不用说当重新定义为执行与其通常操作不同的事情时的技术困难,也许这个重新定义包含在执行修改后的代码后立即\par
重新定义自身的代码。\par
分组可确保环境修改的参数的先前值在环境结束时自动恢复。
顺便说一句,这就是为什么没有chapter
、section
等等环境的原因,乍一看可能很有吸引力:一个非常长的chapter
环境,有许多内部环境,可能会填满 TeX 为恢复值分配的堆栈内存。再次考虑一下 LaTeX 编写时的计算机内存严重受限。
缺点
查看代码,lrbox
了解如何解决在环境中设置框但其值保留在的问题\end{lrbox}
。值得研究的有趣代码,尤其是\hbox{\begingroup\aftergroup}
第一次阅读时可能会感到惊讶;-)
不可能在环境内部定义一个宏,以便它的值在较高级别传播,而无需执行如上所述的特殊技巧;将其传播到上面的两个级别则是双重棘手等等。
解决方案
使用全局定义:\gdef\foo{bar}
将重新定义传播到所有级别。请参阅\global\renewcommand 相当于 \global\def如果您想使用类似 LaTeX 的语法,并使用通常的保护措施。