tabular 基础

排表格时敲下的 &,在 LaTeX 眼里并不是「和号」。它是一个带有特殊类别码的字符——第 4 类,TeX 称之为 alignment tab(对齐制表符)。所以一旦列数写错,报出的错误是 ! Extra alignment tab has been changed to \cr.,用的偏偏是屏幕上根本看不到的词。本页讲解 tabular 环境的三条约定——用 & 分隔单元格、用 \\ 结束一行、用 \hline 画线——并把底下的机制一并揭开。看懂了机制,初学者几乎必然撞上的两个错误 Extra alignment tabMisplaced \noalign,就不再是谜语,而是提示。

tabular 环境的三个符号:&、\\ 与 \hline

tabular 主体中只有三个符号具有意义。& 分隔同一行内的单元格,\\ 结束一行,\hline 在该位置画一条横贯表格全宽的线。\begin{tabular} 紧接着要带一个 必选引数,即列指定。\begin{tabular}{ccc} 声明「三个居中列」,这里定下的列数就是整张表的骨架。& 前后的空格会被忽略,所以在源码里可以自由地用空格把单元格对齐。

latex
\begin{tabular}{ccc}
  \hline
  A & B & C \\
  D & E & F \\
  \hline
\end{tabular}

\\ 并不是表格专用的符号,而是 LaTeX 通用的 换行命令。同一个符号在数学的 pmatrixalign 中同样充当行分隔符。因此「用 & 对齐列、用 \\ 换行」的手感可以从表格直接带到公式里。最后一行后面的 \\ 可以省略,但若要在该行下方画线,就必须先用 \\ 结束这一行再写 \hline——省略了就会径直撞上本页后半讲的 Misplaced \noalign\\ 还接受方括号中的可选引数:\\[2pt] 会在该行下方增加额外的垂直空白,负值则可压紧。

\begin{tabular} 在列指定 之前 还可以带一个方括号的垂直位置可选引数。\begin{tabular}[t]{…} 让表格顶端与周围行的基线对齐,[b] 则对齐底端,默认是表格的垂直中心对齐。不过这里有个陷阱:如果表格的开头或结尾是 \hline,对齐就会发生在 线条的位置,结果并非所期望的样子。array 宏包正是为这个症状准备了 \firsthline\lasthline——用它们替换 \hline,表格就会按正文的第一行或最后一行正确对齐。

Extra alignment tab has been changed to \cr —— 列数对不上

这个错误只表示一件事:该行中的 & 比列指定声明的多。在声明为 {ll} 的表格里写 A & B & C,必然会看到它。TeX 会把多出来的 & 当作行末(\cr)读掉再继续,所以输出不会消失,而是得到一张 错位的表。多数原因是列指定数错了,但另外三种也很常见:在 \multicolumn{3}{c}{…} 已经吃掉全部三列的行里还写了 &;在单元格内写了 \\;以及把 array>{\raggedright} 加在 最后一列。最后这种之所以致命,是因为 \raggedright 重新定义了 \\,行末失效,下一行的 & 于是溢出并触发同样的错误——写成 >{\raggedright\arraybackslash} 即可修复。

反过来,& 少了 却毫无提示。缺少的单元格会被排成空白,既不报错也不警告;所以当表格明显错位却顺利编译时,先怀疑漏写了 &。还有一个诊断线索:在列指定里写了未知字母时,错误措辞取决于载入了哪个宏包。纯 LaTeX 会说 ! LaTeX Error: Illegal character in array arg.,而载入 array 后则是 ! Package array Error: Illegal pream-token。出现哪一个,就说明你的表格实际跑在哪一套实现上。

Misplaced \noalign —— \hline 只能放在行与行之间

这个错误几乎总是同一个原因:没有用 \\ 结束一行就写了 \hline。在最后一行后漏掉 \\ 直接接 \hline,TeX Live 2024 的日志里会连锁出现三条错误——! Misplaced \noalign.! You can't use \hrule here except with leaders.! Missing number, treated as zero.。三条来自同一个原因。原因就在 \hline 的定义里:打开 latex.ltx 可以看到 \def\hline{\noalign{…\hrule \@height \arrayrulewidth …}}\noalign 是 TeX 的原始命令,只表示「把这段材料插入对齐结构的 行与行之间」。在行中途被调用时它无处可去,于是 \noalign 以及本该被它包住的 \hrule 就依次裸露地漏了出来。

latex
% WRONG: the last row was never closed, so \hline has no gap to sit in
\begin{tabular}{ll}
  A & B \\
  C & D
  \hline
\end{tabular}

% RIGHT: close the row first, then draw the rule
\begin{tabular}{ll}
  A & B \\
  C & D \\
  \hline
\end{tabular}

按同样的道理,把 \hline 写在 单元格中间 也会得到同样的三连错误。反过来,\hline 单独跟在 \\ 之后永远是安全的,甚至写成 \hline \\ 造出一个空行也不算错误,只是多出一段可见的空白。另一个在 tabular 周围频繁出现的是 ! LaTeX Error: \caption outside float.,这是把 \caption 写进 tabular 的典型后果:正如下一节所说,标题是 table 的职责。忘写 \end{tabular} 则会得到更贴心的 ! LaTeX Error: \begin{tabular} on input line N ended by \end{document}.,连行号都告诉你。

怎样画线,以及哪些线不该画

横线来自 \hline,竖线来自在列指定中写入 |{|l|c|r|} 会在三列的外侧和各列之间放入竖线,配合 \hline 就把每个单元格变成网格中的格子。\hline\hline 得到双线,两线之间的距离是长度 \doublerulesep(默认 2pt)。线的粗细由长度 \arrayrulewidth 决定,标准文档类的默认值是 0.4pt\cline{2-3} 接受列号范围,只在部分列上画线。以上就是标准 LaTeX 能画的全部线条。

写法作用注意
&分隔同一行的单元格类别码 4;写多了会报 Extra alignment tab
\\结束一行在 align 和 pmatrix 中作用相同
\\[2pt]在行下方增加垂直空白负值可以压紧
\hline横贯全宽的横线粗细为 \arrayrulewidth(默认 0.4pt)
\hline\hline双横线间距为 \doublerulesep(默认 2pt)
\cline{2-3}只在第 2 至第 3 列画线在合并单元格下方画线的惯用法
|写在列指定中的竖线正式表格中通常应避免
||双竖线间距同样是 \doublerulesep

然而在刚才列出的工具中,专业排版的立场是 竖线和双线根本不该使用booktabs 的说明书《Publication quality tables in LaTeX》(Simon Fear 著,TeX Live 2024 所附版本修订于 2020 年 1 月 12 日)给出两条指针:「1. 绝对不要使用竖线。2. 不要使用双线。」作者甚至写道,如果表格左半与右半差别大到似乎需要一条线来分开,那就应该拆成两张表;关于双线他也坦率地补充:曾为一家坚持在总计行上方加一条双细线的出版社工作过,「但那不会是我的选择」。他推荐的替代方案是 粗细不同的横线,配上上下充裕的留白

要紧的是,这些指针 并不适用于所有表格。Fear 明确区分了 formal table(正式表格)——由带标签的列构成的一组数值——与 tableau,即那种大概会缀满图标、还会用颜色的一次性版面,并说明两条指针针对的是前者。他写道,正式表格的版式是「经数百年经验确立的,除非有特别的理由,否则不应改动」。反过来说,给日程表或界面示意图加上网格,本就不在这些指针的射程之内。若要把数值表放进论文,现代的做法是使用 booktabs\toprule\midrule\bottomrule,详见「线条与出版级表格」一页。

当行显得局促、线条看上去贴着字母时,最快的办法是用 \renewcommand{\arraystretch}{1.2} 提高行高倍率(默认值为 1)。列与列之间的空白是长度 \tabcolsep,默认每侧 6pt,因此两列之间合计 12pt。若已载入 array 宏包,还可以使用 \setlength{\extrarowheight}{2pt}——它只增加每行的 高度 而不改变深度,正好对症于线条擦到大写字母顶部的情况。

tabular 与 table 的区别:表格本身不是浮动体

tabular 只负责排 表格的内容:没有编号,没有标题,也没有放置的话语权。它像一段文字那样被处理,所以可以出现在段落中间、列表项里,甚至脚注中。相对地,table浮动体:把它包在 tabular 外面,就能获得 \caption{…} 生成的自动编号标题、\label{…}\ref{…} 提供的交叉引用,以及 [htbp] 这类放置选项。把 \caption 写进 tabular 会得到 ! LaTeX Error: \caption outside float.——正是这一分工化成的错误信息。

document.tex
\begin{table}[htbp]
  \centering
  \caption{Quarterly sales}
  \label{tab:sales}
  \begin{tabular}{lrr}
    \hline
    Quarter & Sales & YoY \\
    \hline
    Q1 & 1{,}200 & +5\% \\
    Q2 & 1{,}350 & +8\% \\
    \hline
  \end{tabular}
\end{table}

% ... and refer to it from the running text:
See Table~\ref{tab:sales}.

把它做成浮动体的理由,与其说是为了编号,不如说是因为 tabular 无法跨页断开。它被排成一个大盒子,若剩余页面容纳不下,就会整体推到下一页,在原处留下一大片空白。放进 table 之后,LaTeX 就能把这个盒子移到后面某页的顶部,并用正文填补空白。[htbp] 是一份愿望清单——此处、页顶、页底、单独一页——LaTeX 会按这个顺序尝试。至于高到任何一页都装不下的表格,本就无法作为浮动体处理;那是能跨页分割的 longtable 的任务。

含长文本的表格:没有 p{width} 就会跑出纸面

lcr 这三个只会把列撑到 内容的自然宽度,在行内绝不换行。把一段长说明直接放进 l 列,列就会伸到那一行的宽度,把表格顶出版心。此时出现的是警告 Overfull \hbox (…pt too wide)——是警告而非错误,所以 文档照样编译通过,只有看 PDF 时才发现问题,这正是它讨厌的地方。如果需要换行,纯 tabular 只提供一个列指定:p{width}。它把单元格排成指定宽度的段落盒,超出宽度的内容会自动流到下一行。

latex
\begin{tabular}{l p{5cm} r}
  \hline
  Item & Description & Price \\
  \hline\hline
  Apple  & A large honey-cored variety, popular as a gift. & 380 \\
  Orange & A small, sweet citrus that peels by hand.       & 120 \\
  \hline
\end{tabular}

第 1 列是自然宽度的左对齐,第 3 列右对齐,只有第 2 列是宽 5cm 的 p 列。长说明会在这 5cm 处换行,于是只有那个单元格变成多行,整行随之变高。把同一句话放进 l 列,它只会横向伸展而不换行——这正是 p{width} 带来的决定性差异。表头行下方是 \hline\hline 双线,上下端各用一条 \hlinep 列的内容默认顶部对齐并两端对齐,其内部的段落缩进 \parindent 被设为 0。若想让单元格垂直居中或底部对齐,或者用左对齐(ragged right)而非两端对齐,就进入了 array 宏包的领域,详见「列指定详解」。