退出自定义环境时宏会失去价值

退出自定义环境时宏会失去价值

我正在尝试定义自己的自定义环境,但遇到了这个问题:当我在环境的启动代码中设置一个宏时,该宏会在环境结束时失去其值。

更准确地说,使用以下 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. 这里发生了什么?
    这是某种局部作用域,其中局部宏会遮盖全局宏的值吗?还是我还未理解的其他机制?

  2. 我如何才能以某种方式为环境内的宏分配一个值,以便之后可以实际使用它?

答案1

当你使用以下方式定义环境时

\newenvironment{envname}{begincode}{endcode}

并使用它

\begin{envname}
body
\end{envname}

LaTeX 大致执行

\begingroup
begincode   % as \envname
body
endcode     % as \endenvname
\endgroup

这意味着在 内发生的所有分配begincode都是endcodebodycode环境中隐含的组本地进行的。

在您的情况下,\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,,,等等。flushleftenumerateitemize

这些环境中的每一个都可能设置很多参数,在这里我们看到维护堆栈会变得很痛苦;只要想想enumerate嵌套在中itemize。更不用说当重新定义为执行与其通常操作不同的事情时的技术困难,也许这个重新定义包含在执行修改后的代码后立即\par重新定义自身的代码。\par

分组可确保环境修改的参数的先前值在环境结束时自动恢复。

顺便说一句,这就是为什么没有chaptersection等等环境的原因,乍一看可能很有吸引力:一个非常长的chapter环境,有许多内部环境,可能会填满 TeX 为恢复值分配的堆栈内存。再次考虑一下 LaTeX 编写时的计算机内存严重受限。

缺点

查看代码,lrbox了解如何解决在环境中设置框但其值保留在的问题\end{lrbox}。值得研究的有趣代码,尤其是\hbox{\begingroup\aftergroup}第一次阅读时可能会感到惊讶;-)

不可能在环境内部定义一个宏,以便它的值在较高级别传播,而无需执行如上所述的特殊技巧;将其传播到上面的两个级别则是双重棘手等等。

解决方案

使用全局定义:\gdef\foo{bar}将重新定义传播到所有级别。请参阅\global\renewcommand 相当于 \global\def如果您想使用类似 LaTeX 的语法,并使用通常的保护措施。

相关内容