放进 table 环境的表格,无论有多少行都 不会跨页断开——而且溢出时不会报错。在 TeX Live 2024 上把一张 119 行的表格包进浮动体,输出是 一页而不是三页,其余的行悄悄滑出了纸面下沿。打破这堵墙的是 LaTeX 的 longtable 宏包。但 longtable 真正的工作并不是分页,而是 让每一页的列宽保持一致——为此它把实测值写进 .aux 文件,并用 Table widths have changed. Rerun LaTeX. 这条警告要求你再编译一次。本页跟踪这第二次编译到底买到了什么,并与跳过它的 supertabular 做实测对比。
为什么 table 环境里的表格不会跨页
因为 table 和 figure 一样是 浮动体,而 LaTeX 把浮动体当作「能放进一页的、不可分割的一个盒子」。盒子会整体被搬到页首或页尾;既然要整体搬运,就不能从中间切开。所以内部的 tabular 无论多高都不会被拆分,放不下就直接跑出纸面。麻烦的是 这并不会产生警告。用同样的 119 行数据实验,包进浮动体的版本输出 1 页,不包的版本输出 3 页,一条错误信息也没有。这是那种要把 PDF 翻到最后才会发现的事故。
也就是说,「跨页表格」与浮动体机制本身根本不兼容。需要的是把表格 放进与正文相同的流中,像段落一样交给 TeX 自己的分页算法 处理。这就是 longtable。它由 David Carlisle 编写——他也是 array、tabularx、dcolumn 的作者——随每个 LaTeX 安装附带的 tools bundle 一起发行,无需另行安装。
longtable 怎么写 — 与 tabular 有什么不同
写法几乎与 tabular 相同。载入 \usepackage{longtable},用 \begin{longtable}{l l r} 开始——列指定是必选引数——用 & 分列、用 \\ 结束行。l、c、r、p{width} 等列类型以及 \hline、\multicolumn 都照常可用。只有三点不同。第一,绝不要用 table 环境包住它:那会让它退回浮动体,重现上文的一页问题。第二,列指定 之前 可以写位置用的可选引数 [c]、[r]、[l]。第三,longtable 自带标题(见下文)。分页只发生在行与行之间或 \hline 处,绝不会在 p 列的单个单元格中间断开。
位置和上下左右的留白由若干长度决定,\begin{document} 之后随时可以修改。在 TeX Live 2024 上实测的默认值是:\LTleft 与 \LTright 都是 0pt plus 1fill——两侧推力相同,这正是表格 居中 的原因。若要左对齐,保留 \setlength{\LTright}{0pt plus 1fill} 并设 \setlength{\LTleft}{0pt}。表格上下的空白是 \LTpre 与 \LTpost,默认都是 12pt plus 4pt minus 4pt。
每页重复表头与表脚 — endhead 与 endfoot
在表格开头排列 四个标记,各自声明「到此为止的行要在哪些页面重复」。标记放在对应行的末尾,取代 本来的 \\。四个都可省略;如果所有页面用同一个表头,只写 \endhead 就够了。
| 标记 | 前面的行出现在哪里 | 典型内容 |
|---|---|---|
\endfirsthead | 仅第一页的表头 | \caption{…} 和列标题 |
\endhead | 除第一页外每一页的表头 | “(continued)” 加列标题 |
\endfoot | 仍会继续的页面表脚(除最后一页) | “Continued on next page” 行 |
\endlastfoot | 表格最末尾,仅最后一页 | 收尾横线或注记 |
\kill | 不出现在任何地方——只用于测量宽度 | 写有预计最宽内容的假行 |
\documentclass{article}
\usepackage{longtable}
\begin{document}
\begin{longtable}{l l r}
% head of the first page only
\caption{Annual sales log}\\
\hline
Date & Item & Amount \\
\hline
\endfirsthead
% head of every later page
\multicolumn{3}{l}{\small (continued)}\\
\hline
Date & Item & Amount \\
\hline
\endhead
% foot of every page that continues
\hline
\multicolumn{3}{r}{\small Continued on next page}\\
\endfoot
% foot of the last page
\hline
\endlastfoot
% everything below the markers is real data
2026-01-05 & Apple & 380 \\
2026-01-06 & Orange & 120 \\
% ...rows continue; pages are broken automatically...
\end{longtable}
\end{document}顺序很关键。四个标记必须 按这个顺序排在表格最上方,而 写在它们下面的行才是真正的数据,会被流入各页。反过来说,表格内容从 \endlastfoot 之后的第一行开始。破坏这个顺序,本想当表头的行就会变成数据的第一行。\kill 稍有不同:它生成一行 只测量、不打印 的内容。当表头很短、却在第 100 页才出现一个很长的值时,在开头写一行含最大宽度样本的 \kill,就能让列从一开始就足够宽。
Table widths have changed. Rerun LaTeX. — 为什么要编译两次
为了让每一页的列宽一致。 longtable 逐页排版表格,但某一页最宽的单元格要等这一页排完才知道。若各页独立排版,各页就会有各自的列宽。因此 longtable 把测得的宽度写进 .aux 文件,并在 下一次运行时读回,作为所有页面共同的宽度。这就是第一次编译会打印下面这条信息的原因。
Package longtable Warning: Column widths have changed
(longtable) in table 1 on input line 24.
Package longtable Warning: Table widths have changed. Rerun LaTeX.忽略这条警告的代价可以实测。从一张跨 3 页的表格 PDF 中读取右侧列标题“Amount”的位置:第一次运行时它位于 x = 320.41pt,而下方数据位于 x = 488.69pt——标题和数字站在不同位置。运行第二次后,标题移到 x = 471.53pt,且三页完全一致。也就是说,第一次的输出是一张列过窄、标题左移的表格。这正是「编译一次表格就不对」的真相。
这个 .aux 文件是可以打开看的。下面就是一张三列 longtable 留下的内容。\LT@i 表示「本文档中的第一张 longtable」,第二张则是 \LT@ii。每个条目的第二个引数是该列的实测宽度,下一次运行会直接使用这些数字。
\gdef \LT@i {\LT@entry
{2}{58.6668pt}\LT@entry
{2}{274.36153pt}\LT@entry
{1}{47.55566pt}}测量并不是一次完成,而是 按块进行,块的大小由计数器 LTchunksize(默认 200 行)决定。如果行数极多的表格耗尽了 TeX 的内存,可以用 \setcounter{LTchunksize}{10} 调小;调大则减少 .aux 的往返次数,有时收敛更快。实务上,用 latexmk 就完全不必操心这些往返——它会一直重跑到警告消失为止。
标题与编号 — LTcapwidth 默认是 4 英寸
longtable 不是浮动体,却 自带 \caption{…}。它推进标准的 table 计数器,所以标题会自动编号为「表 N」,并出现在 \listoftables 中。由于标题是表格布局的一部分,规矩是把它 写在表头块中、\endfirsthead 之前,并用 \\ 结束该行,即 \caption{…}\\。宽度上有个意外:标题宽度 \LTcapwidth 默认是 4 英寸,即 289.08pt。而 article 的 \textwidth 是 345pt,所以默认情况下 标题比正文窄 56pt。想与正文同宽,就写 \setlength{\LTcapwidth}{\textwidth}。
其余是些细节约定。不想要编号 时用 \caption*{…}——没有编号,也不进表目录。后续页面上「(continued)」这类 附属标题,应写成 可选引数为空的 \caption[]{…},以免在表目录中重复列出。而交叉引用的 \label{…} 绝不能放进会重复的 \endhead:那样同一个标签会被定义与页数相同的次数。请放在 \endfirsthead 中,或表格主体的第一行。
booktabs 的线条在分页处会怎样
\toprule、\midrule、\bottomrule 可以直接在 longtable 中使用:booktabs 在 2000 年的版本加入了 longtable 兼容,booktabs.sty 中至今保留着 \ifx\longtable\undefined 分支。但行为上有一处不对称——longtable 为了分页会 自动把 \hline 变成双线(在断裂处上下各画一条),而 booktabs 的线条不会加倍。因此,如果某条线恰好落在分页位置,按 booktabs 手册自己的说法只能 手工处理,通常是删掉那条碍事的线。实务上的正解是把线条放进 \endfoot 和 \endhead,从设计上让断裂处永远不出现线条。
还有一份额外好处。本来只属于 \cmidrule 的 左右修剪引数,在 longtable 内部也能作用于 \toprule、\midrule、\bottomrule。写 \toprule(r) 会让右端向内收,\midrule(lr) 则两端都收。在 TeX Live 2024 上同时载入 longtable 与 booktabs 实测通过。反过来说,在普通 tabular 中写不了同样的东西;booktabs 的作者在手册里半开玩笑地说,如果非要修剪过的线条,那就干脆把所有表格都做成 longtable。
supertabular 与 xtab — 只需一次编译,代价是列会跳
能做跨页表格的宏包不只有 longtable。supertabular(Johannes Braams 与 Theo Jurriens)以及它的衍生品 xtab(Peter Wilson)采取了完全不同的策略。longtable 是「排出整张表,再交给 TeX 分页」,而这两者是 「测量页面剩余高度,再新建一个刚好放得下的 tabular」——也就是逐页排列彼此独立的 tabular。因此它们不需要 .aux 往返,一次编译即可完成。
但代价会直接显示在版面上。用同样的 119 行数据,只在第 90 行混入一个特别长的条目来对比。在 supertabular 下,列标题“Amount”在 第 1、2 页位于 x = 249.16pt,而在含长条目的第 3 页位于 x = 471.53pt——翻页的瞬间表格宽度就变了。同样的数据用 longtable,第二次编译后 三页都是 x = 471.53pt。编译两次,买到的正是这份一致性。写法也不同:supertabular 在 环境之外 用 \tablefirsthead、\tablehead、\tabletail、\tablelasttail 声明表头与表脚,标题用 \topcaption / \bottomcaption。xtab 在此基础上增加了诸如指定最少行数以免断口难看之类的功能。
既能换行又能跨页的 X 列 — xltabular 与 ltxtable
longtable 的列指定与 tabular 相同,所以长文本只能放进固定宽度的 p{width} 列换行。若想要一列自动扩展占满剩余宽度,那就需要 tabularx 的 X 列,但 tabularx 环境本身不能跨页。有两座桥梁能连通两者。xltabular(Rolf Niepraschk 与 Herbert Voß)是「带 X 列的 longtable」,写成 \begin{xltabular}{\linewidth}{l X r},接收 总宽度与列指定,\endfirsthead 等标记也照常可用。它内部载入 ltablex,但不改变已有 tabularx 环境的行为。
\documentclass{article}
\usepackage{xltabular}
\begin{document}
\begin{xltabular}{\linewidth}{l X r}
\caption{Glossary}\\
\hline
Term & Description & Page \\
\hline
\endfirsthead
\hline
Term & Description & Page \\
\hline
\endhead
longtable & Breaks across pages; sits in the text flow, not in a float. & 12 \\
xltabular & A longtable whose X column takes the leftover width. & 34 \\
\end{xltabular}
\end{document}另一座桥是同样出自 David Carlisle 之手的 ltxtable。这里要把表格主体 写进单独的文件,再用 \LTXtable{\textwidth}{文件名} 读入。文件中是带 X 列的 longtable;ltxtable 先跑一遍 tabularx 的宽度计算,再把结果交给 longtable。拆分文件确实麻烦,但这样 tabularx 的反复展开就不会与 longtable 的 .aux 往返互相干扰,长表更稳定。选择很简单:图省事用 xltabular,大而复杂的表出现怪现象时用 ltxtable。另有一个小差别:xltabular 只有在你确实写了 \caption 时才会消耗 table 编号。