我不相信我是第一个问这个问题的人,但是我确实没有通过搜索找到这个问题。
\LaTeX
在它的定义中\addvspace
,我通常使用\vspace
添加垂直空间。所以这让我想知道它们的区别。
答案1
\vspace
为了理解和之间的区别\addvspace
,让我们看一下这两个宏的定义(来自 Texlive 2022 的latex.ltx
):
\vspace
\vspace
定义为
\DeclareRobustCommand\vspace{\@ifstar\@vspacer\@vspace}
区分带星号的版本(\@vspacer
)和不带星号的版本(\@vspace
)。
无星号版本
无星号版本定义为
\def\@vspace #1{%
\ifvmode
\@vspace@calcify{#1}%
\vskip\z@skip
\else
\@bsphack
\vadjust{\@restorepar
\@vspace@calcify{#1}%
\vskip\z@skip
}%
\@esphack
\fi}
这里发生的情况是,我们首先查看是否处于垂直模式(\ifvmode
)。
如果我们处于水平模式(即,我们在一个段落内),那么我们告诉 LaTeX 等待当前行被渲染(\vadjust
),然后我们执行与垂直模式时相同的代码:
\@vspace@calcify{#1}%
\vskip\z@skip
宏\@vspace@calcify{#1}%
在其他地方定义为
\def\@vspace@calcify#1{\begingroup\setlength\skip@{#1}\vskip\skip@\endgroup}
这里,我们临时 ( \begingroup...\endgroup
) 将长度寄存器 ( ) 设置为( )\setlength\skip@
参数中定义的数量,并使用该寄存器作为参数 ( )调用原语,将垂直跳跃插入输出流。临时宏的设置已完成,因此包中的宏可以在 中使用。\vspace
#1
\vskip
\vskip\skip@
calc
#1
第二行,在实际跳过后\vskip\z@skip
添加零长度 ( )。这样做是为了防止宏消耗我们明确希望存在的垂直跳过。\z@skip=0pt plus0pt minus0pt
\removelastskip
(我们将忽略\@bsphack
和因为它们与问题无关)\@esphack
\@restorepar
加星标版本
\vspace*
定义为
\def\@vspacer#1{%
\ifvmode
\dimen@\prevdepth
\hrule \@height\z@
\nobreak
\@vspace@calcify{#1}%
\vskip\z@skip
\prevdepth\dimen@
\else
\@bsphack
\vadjust{\@restorepar
\hrule \@height\z@
\nobreak
\@vspace@calcify{#1}%
\vskip\z@skip}%
\@esphack
\fi}
它基本上是相同的,但有一个重要的补充:我们在跳过(\hrule \@height\z@
)之前添加了一个零高度(和宽度)规则。这样,我们强制 LaTeX 进入水平模式,同时防止潜在的分页符(\nobreak
),我们立即返回垂直模式(这是使用 时自动完成的\vskip
)。这具有以下效果:假设我们位于页面的最顶部并处于垂直模式。在这种特定情况下,vskips 通常不适用。现在,通过“打破”垂直模式,TeX 不再“位于”类型区域的“最顶部”(因为实际顶部和当前位置之间存在 hmode 内容,尽管空的hmode-stuff)。 因此,vskip是已应用。
通过第二次添加,\dimen@\prevdepth
以及稍后\prevdepth\dimen@
在垂直模式下,我们将有关我们是否位于类型区域顶部的信息“移动”到之后的内容\vspace*
(“稍微”简化)。
\addvspace
\addvspace
定义为
\def\addvspace#1{%
\ifvmode
\if@minipage\else
\ifdim \lastskip =\z@
\@vspace@calcify{#1}%
\else
\setlength\@tempskipb{#1}%
\@xaddvskip
\fi
\fi
\else
\@noitemerr
\fi}
首先,我们检查是否处于垂直模式,如果不是,则抛出错误。这里,我们有\vspace
和之间的第一个真正区别\addvspace
:\addvspace
只能在垂直模式下使用,而\vspace
可以在垂直和垂直模式下使用和水平模式。
第二个条件 ( \if@minipage
) 检查我们是否处于环境的顶部minipage
。由于“true”分支为空,因此 any\addvspace
不执行任何操作。第二个不同之处。
第三个条件是\ifdim \lastskip =\z@
,它检查最后一个垂直跳跃(存储在\lastskip
)是否为 0pt(\z@
)。如果是,我们应用\@vspace@calcify{#1}
我们已经知道的宏。
然而,如果在之前有一个非零的垂直跳过\addvspace
,我们将参数存储在临时长度寄存器中\@tempskipb
并调用宏\@xaddvskip
,该宏定义为
\def\@xaddvskip{%
\ifdim\lastskip<\@tempskipb
\vskip-\lastskip
\vskip\@tempskipb
\else
\ifdim\@tempskipb<\z@
\ifdim\lastskip<\z@
\else
\advance\@tempskipb\lastskip
\vskip-\lastskip
\vskip \@tempskipb
\fi
\fi
\fi}
在这里,我们检查存储的长度是否大于最后一个 skip ( \ifdim\lastskip<\@tempskipb
)。如果是,则删除最后一个 skip ( \vskip-\lastskip
) 并添加临时 skip ( \vskip\@tempskipb
)。
如果最后一次跳过大于我们的临时跳过,我们检查临时跳过和最后一次跳过是否为负。
如果临时跳过为负数,并且最后一个跳过为负数,则我们什么也不做。这意味着,只有最后一个跳过有效,并且我们提供的数量将被丢弃。当两个值都为正数时,也会发生同样的情况,但我们的临时值仍然小于最后一个跳过的值(因为条件中\addvspace
没有分支)。\else
\ifdim\@tempskipb<\z@
如果临时跳跃为负,而最后一个跳跃为正,我们将最后一个跳跃的值添加到临时跳跃(\advance\@tempskipb\lastskip
),删除整个最后一个跳跃(\vskip-\lastskip
),然后插入一个垂直跳跃,其中包含临时跳跃的新值(\vskip \@tempskipb
)。例如,如果最后一个跳跃是10pt
,我们加上\addvspace{-3pt}
,那么我们得到一个垂直跳跃-3pt + 10pt = 7pt
。
\vskip\z@skip
请注意,的定义中没有\addvspace
,这意味着\removelastskip
做影响跳过插入\addvspace
,这是\addvspace
和之间的另一个区别\vspace
。
您可能还会注意到, 的定义\@xaddvskip
缺少对 的调用\@vspace@calcify
。这个中间函数的唯一目的是传递 skip 的参数,\setlength
以便calc
包中的宏(挂接到)可以在参数中使用。我们在调用 之前(当我们设置为参数的值时\setlength
)已经这样做了,因此不需要额外的步骤。\@xaddvskip
\@tempskipb
总结
\vspace{X}
添加X
垂直空间量,检查上方\addvspace{Z}
是否有垂直空间,并且仅在或 时添加。Y
\addvspace{Z}
Z
Z>Y
Y=0pt
- 如果
Z<0pt
和Y>0pt
,Z+Y
则添加为垂直空间 \addvspace
环境顶部minipage
不执行任何操作,而环境顶部则\vspace
执行空格操作。\vspace
可在垂直和水平模式下使用,\addvspace
只能在垂直模式下使用- 添加 的跳过
\addvspace
可以通过 删除\removelastskip
,添加 的跳过\vspace
不能。
关于“最后跳过”的最后一点说明
正如我们所发现的,\addvspace
通过将其参数中的值与 的值进行比较,考虑了“跳过之前”本身\lastskip
。但是,有些情况下\lastskip
可能会受到其他命令的干扰(例如,\write
和\penalty
),更具体地说是将非粘合项添加到垂直列表的命令,即使它们不会产生任何可见的输出。在这些情况下,数量\addvspace
显然是添加到已经存在的垂直跳跃,你最终可能会得到比其他地方大得多的空间。