LaTeX 从头到尾只读一遍文档。所以当第 2 页写下 \ref{fig:leaf},而它指向的图在第 40 页时,那个编号还根本不存在——LaTeX 的整套交叉引用机制,也就是 \label 与 \ref,正是为了解决这一个问题而生:引用出现在目标之前。办法朴素得出人意料:第一遍运行把编号写进 .aux 文件,第二遍再读回来。本页顺着这条线索,讲两遍编译、中途出现的粗体 ??、把 \label 放在 \caption 之前会报出错误编号的原因,以及 cleveref 和 varioref 带来的改进。
\label 与 \ref — 用名字称呼,不写编号
交叉引用只靠两个命令。\label{key} 把名称 key 绑定到该处 最近一次递增的计数器 的值上,纸面上什么也不打印。\ref{key} 则把这个名称所代表的 编号本身 插入正文。计数由 LaTeX 完成,你一个数字都不用敲。key 是任意字符串;因为标签跟着对象走,你可以重排图片顺序,或者把整节搬到另一章,所有引用都会跟着更新。手写「图 3」的文档经不起这样一次移动,而这样的文档毫发无损。
\section{Introduction}
\label{sec:intro}
\begin{equation}
\label{eq:euler}
e^{i\pi} + 1 = 0
\end{equation}
\begin{figure}
\centering
\includegraphics{leaf}
\caption{Vein structure of a leaf}
\label{fig:leaf} % after \caption, always
\end{figure}
As shown in Section~\ref{sec:intro}, equation~\eqref{eq:euler}
and Figure~\ref{fig:leaf} agree on page~\pageref{fig:leaf}.有一点几乎人人都会栽跟头:\ref 返回的 只有编号。「图」「公式」「第几节」这些词不会跟着出来,需要自己写,而且要用 不断行空格 ~(tie)而非普通空格把词和编号连起来。写成 Figure \ref{fig:leaf},一行可能正好在「Figure」后结束,把「3」孤零零留到下一行;写成 Figure~\ref{fig:leaf} 就不会在那里断开。手写类型词这件小事,正是后面 \cref 和 \autoref 要替你接管的部分。
为什么要编译两次 — .aux 文件与 ??
交叉引用 不会在一次编译中定型。每遇到一个 \label,LaTeX 就把对应编号和页码写进与正文同基名的 .aux 文件;而 \ref 能读到的,只有 上一次 运行写进去的内容。因此第一遍时目标值根本不存在:输出中出现 粗体 ??,日志里并排出现两条警告。这两条都值得记住,因为它们正是人们会敲进搜索框的字符串。
LaTeX Warning: Reference `fig:leaf' on page 1 undefined on input line 17.
LaTeX Warning: There were undefined references.
LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.这个 ?? 不是意外的副产品,而是 写进 LaTeX 内核的规定。latex.ltx 中的 \@setref 在名称未定义时排出 \reset@font\bfseries ??,并在紧接着的一行发出「Reference … undefined」警告。用粗体是有意为之:粗体不会融进周围的字体,缺口一眼可见。再编译一次,.aux 中的值就会读回来,?? 变成真正的编号。如果编号前后移动导致引用值改变,警告会再次出现,所以正确做法是 重复到警告消失为止(实践中通常两遍足够)。latexmk 会替你转这个循环,Overleaf 也在后台处理多次,所以在那里很少见到 ??。
.aux 是普通文本文件,可以直接打开阅读。每个标签写下一行 \newlabel,行中含有 五个字段:编号、页码、标题文字、链接目标的锚点名,以及一个备用字段。正因为有第三个字段,\nameref 才能插入标题本身;正因为有第四个字段,hyperref 才知道点击该跳到哪里。引用出问题而原因不明时,最短的路径就是打开 .aux 找那一行。整行不见,说明 \label 根本没被采集;值看着不对,那就是它的摆放位置有问题——正是下一节的主题。
% one line per label: {number}{page}{title}{anchor}{spare}
\newlabel{sec:intro}{{1}{1}{Introduction}{section.1}{}}
\newlabel{fig:leaf}{{2}{3}{Vein structure of a leaf}{figure.2}{}}当 \ref 给出错误编号 — \label 要放在 \caption 之后
图的引用与图的编号对不上——这个症状的原因几乎无一例外是 \label 的位置。\label 并不是因为「身处 figure 环境之内」才取到图号的。它取的是 紧邻其前递增的那个计数器 的值,而 figure 环境本身根本不递增任何计数器。真正推进计数器的是 \caption。因此,紧跟 \begin{figure} 写下的 \label 捕获的不是图号,而是当时最后递增过的计数器——多半是当前所在的节号或章号。
与其解释,不如亲手跑一遍。把一张图放进第 7 节里,在 \caption 的 前 和 后 各贴一个标签,用 TeX Live 2024 的 pdfLaTeX 编译,.aux 中会写着:放在前面的标签是 7(节号),放在后面的是 1(图号)。而且一条警告也不会出现。两者都是合理的编号,LaTeX 无从判断哪个是错的。正因为它不会报错停下,这类事故常常溜过评审一路进到印刷。
\section{A}\section{B}\section{C}
\section{D}\section{E}\section{F}\section{G} % we are now in section 7
\begin{figure}
\label{fig:before} % WRONG: no counter stepped yet -> picks up 7
\centering\rule{2cm}{1cm}
\caption{A leaf} % <- this is what steps the figure counter
\label{fig:after} % RIGHT: picks up 1
\end{figure}
% .aux after two runs:
% \newlabel{fig:before}{{7}{1}{}{}{}}
% \newlabel{fig:after}{{1}{1}{}{}{}}规则可以浓缩成一句:把 \label 紧放在产生编号的那个命令之后。 图和表就是 \caption 之后(写在 \caption 的参数里也行);标题就是 \section 之类的紧后面;带编号的公式就在 equation 环境内部;列表项就在 \item 之后。一张图里若有多个 \caption 而只贴一个标签,它指向哪个编号取决于你放在了哪个 \caption 之后。至于把 \label 写在 \caption 之前,可以认为唯一站得住脚的理由就是根本没有 \caption。
\pageref 与标签的命名方式
如果想指向的是 页码 而不是编号,用 \pageref{key}。它接受同一个标签,返回该 \label 被排在哪一页 的页码,于是可以像 Figure~\ref{fig:leaf} on page~\pageref{fig:leaf} 这样把编号和页码并列写出。从结构上看,它不过是把 \newlabel 那一行的第二个字段还给你而已。在读者真会一页页翻的文档里——长篇报告、装订成册的论文——它最见效。
key 可以随意命名,但普遍做法是给它加上表示 目标类型 的前缀:sec:、fig:、tab:、eq:、ch:、lst:。这样有两点好处。第一,按内容命名,比如 fig:leaf,重排图片顺序时不必改名;而像 fig:3 这样的名字,一旦插入第二张图就立刻变成谎言。第二,看到 \ref{fig:...} 就立刻知道目标是什么类型,这让你日后把整个文档切换到 \cref 时,可以机械地通读自己的源码。
| 前缀 | 指向的对象 |
|---|---|
ch: | 章(\chapter) |
sec: | 节与小节(\section、\subsection) |
fig: | 图(figure 中 \caption 之后) |
tab: | 表(table 中 \caption 之后) |
eq: | 带编号的公式(equation 等环境内) |
lst: | 代码清单(listings 的 \caption 之后) |
\eqref — 让公式编号自带括号(amsmath)
公式编号按惯例带括号书写(「由公式 (3)」),但普通 \ref 只返回 3。加载 amsmath 后可用的 \eqref{key} 会补上括号,返回 (3)。这里真正的好处不在于少敲几个键,而在于 字形:\eqref 产生的括号无论周围是什么字体都始终是直立体,所以在正文为斜体的定理环境里,括号仍然是竖直的。若手写 (\ref{eq:euler}),那对括号就会跟着斜体一起倾斜。
cleveref — \cref 与 \Cref 自动处理类型词和单复数
cleveref 把引用提升了一档。\cref{key} 会自动在编号前加上表示类型的词(默认是缩写「fig. 1」,加 noabbrev 选项则是「figure 1」),\Cref{key} 把首字母大写,让引用可以放在句首。它真正的强项是 多项与范围:用不带空格的逗号分隔传入标签——\cref{fig:a,fig:b,fig:c}——它会排序并合并编号,过程中 还把类型词改成复数,给出「figs. 1, 2 and 4」这样的结果。更妙的是,编号连续时会自动折叠成范围,所以上面这三个会输出「figs. 1 to 3」。\crefrange{first}{last} 则是 只写两端标签 就得到同样缩约的命令。
前置的词可以用 \crefname{type}{singular}{plural} 重新定义(首字母大写的版本是 \Crefname)。本地化也用这个办法:中文里单数复数给同一个词即可,例如 \crefname{figure}{图}{图}。若希望全文始终大写,加载时带上 capitalise 选项。反过来,在只想要编号、不要类型词的场合——括号内、表格单元格里——用 \labelcref{key} 就能得到与普通 \ref 完全相同的结果。
关于加载顺序有一条绝对规则:cleveref 要放在 hyperref、varioref 和 amsmath 之后,这三者它都会检查。cleveref 依靠检测 hyperref 已定义的内容来构建自己的命令,反过来根本行不通。所幸它不会悄悄坏掉——TeX Live 2024 附带的 cleveref.sty 会在 \begin{document} 处检查顺序,并以下面的信息明确中止。但同时使用 varioref 时情况不同:cleveref 手册规定顺序为 varioref → hyperref → cleveref,并提醒顺序弄错时,引用可能指向完全不相干的对象,而输出和日志里都不会出现任何警告。
\usepackage{varioref}
\usepackage{hyperref}
\usepackage{cleveref} % always last of the three
% If cleveref is loaded before hyperref, TeX Live 2024 stops with:
% ! Package cleveref Error: cleveref must be loaded after hyperref!.
% Package load order is wrong: load cleveref *after* hyperref.
\cref{fig:a,fig:b,fig:c} % figs. 1 to 3 (consecutive: compressed)
\cref{eq:euler,eq:max} % eqs. (1) and (4)
\Cref{sec:intro} explains ... % Section 1 explains ...
\labelcref{fig:a} % 1 (number only, like \ref)
% with \usepackage[noabbrev]{cleveref} the same lines give
% figures 1 to 3 / equations (1) and (4)varioref — 写成「下一页的图」而非光秃秃的页码
在装订成册的文档里,「下一页的图 3」比机械的「第 12 页的图 3」读起来自然,若两者同处一个跨页,「对页的图 3」更好。varioref 的 \vref{key} 会输出与 \ref 相同的编号,然后 查看引用处与目标的页面关系,补上合适的措辞;同页时则什么也不加。若只需要页码提示,用 \vpageref{key},它的两个可选参数分别设定同页时的措辞和不同页时的引导语。
这份聪明是有代价的。\vref 输出什么,取决于排版结果——东西落到了哪一页——而它输出内容的长度本身又可能挪动分页位置,因此有时需要 额外一次编译 才能稳定下来。日志里反复出现「Rerun to get cross-references right」时,多半就是这个原因。与 cleveref 同时加载时,\vref 会改用 cleveref 的格式排版,所以两者都用的话,在遵守顺序(varioref → hyperref → cleveref)的前提下,把整个文档统一到 \cref 一系会更省心。
\usepackage{varioref}
See~\vref{fig:leaf}.
% same page : See figure 3.
% next page : See figure 3 on the next page.
% far away : See figure 3 on page 12.
The data is summarised \vpageref[above][]{tab:data}.如果拿不定主意,实用的答案是 把整个文档统一到 cleveref。类型词、单复数、多项引用和范围它都自动包办,于是「明明是表却写成 Figure~\ref{tab:...}」这类经典失误在原理上就不可能发生。\ref 与 \pageref 对理解机制仍不可或缺,\eqref 在数学中、\vref 在装订成册的作品中,也都依然当用。下表汇总了各个命令分别输出什么。
| 命令 | 输出内容 | 来自 |
|---|---|---|
\label{key} | 给刚递增的计数器贴标签;不打印任何内容 | LaTeX 内核 |
\ref{key} | 仅编号(如 3) | LaTeX 内核 |
\pageref{key} | 该标签被排出的页码 | LaTeX 内核 |
\eqref{key} | 带直立括号的公式编号(如 (3)) | amsmath |
\autoref{key} | 类型词 + 编号,整体为链接(如 Figure 3) | hyperref |
\nameref{key} | 目标标题的文字本身 | hyperref |
\vref{key} | 编号 + 页码提示(「下一页」等) | varioref |
\cref{key} | 类型词 + 编号;自动处理多项、范围与单复数 | cleveref |
\Cref{key} | 首字母大写的 \cref,用于句首 | cleveref |
\crefrange{a}{b} | 连续范围(如 figures 1 to 3) | cleveref |
\labelcref{key} | 仅编号——不带类型词的 \cref | cleveref |