LaTeX 其实没有真正意义上的「合并单元格」。读一读 latex.ltx 就会发现,横向的 \multicolumn 是从 \multispan,也就是 TeX 的原始命令 \omit 开始的——\omit 的意思是「丢弃 这个单元格的列模板」。它并没有把相邻格粘在一起,只是无视既定格式、自行占据宽度。纵向的 \multirow 更大胆:它不过是把文字放进一个足够高、会垂到下方各行的盒子里。这两种实现几乎解释了本页所有的绊脚点,从 ! Misplaced \omit. 这样的错误,到文字压到下一行上的事故。此外还会看看真正棘手的部分——如何用 \cline 和 \cmidrule 在跨越的单元格下面画线。
横向合并 — \multicolumn,以及它为何必须位于单元格开头
写法是三引数的 \multicolumn{n}{spec}{text}:n 是跨越的列数,spec 是 合并后那一个单元格 的列指定,text 是内容。它内置于标准 LaTeX,无需宏包。关键在于 spec 描述的是 合并后的一列,不会继承原来 n 列的指定——所以必须只写 l、c、r 之一。写成两个,如 \multicolumn{2}{cc}{…},就会先后报出 ! Missing \endgroup inserted. 和 ! Extra alignment tab has been changed to \cr. 而崩掉。反过来,spec 里可以写竖线 | 或 @{…},从而 仅对这个合并单元格 覆盖线条和列间空白。
\multicolumn 必须位于单元格的最开头。在写了别的东西之后再调用,例如 X \multicolumn{1}{c}{Y} & Z,就会得到 ! Misplaced \omit.。正如开头所说,\multicolumn 的实体就是 \omit,那是一条声明「不要使用这个单元格的模板」的原始命令——这样的声明只有在单元格开始之前才有意义。这就是错误信息以 \omit 而非 \multicolumn 之名出现的原因。
\begin{tabular}{lcc}
\hline
\multicolumn{3}{c}{Sales summary} \\
\hline
Item & H1 & H2 \\
\hline
Domestic & 120 & 145 \\
Overseas & 80 & 96 \\
\hline
\end{tabular}第一行用 \multicolumn{3}{c}{Sales summary} 把原本应分成三列的一行折叠成一个居中单元格,作为整张表的标题。即使是合并行,该行的单元格合计仍然 必须等于声明的列数;这里 \multicolumn{3}{…} 已承担全部三列,所以不需要 &。还值得记住 把 n 设为 1 的用法:它不跨任何列,但可以 只覆盖该单元格的对齐方式,或 去掉该位置的竖线 |。若只想让数字列(r)的表头居中,写 \multicolumn{1}{c}{Header};若想在带外框的表中只去掉某一格的竖线,只要不在 spec 里写 | 即可。
在带外框的表格中把两个 \multicolumn 并排放置时,竖线只应写在其中一个上。若在 \multicolumn{2}{|c|}{left} 之后接着写 \multicolumn{2}{|c|}{right},交界处会 叠着画出两条线,于是那一条看起来明显比别处更粗——把 \arrayrulewidth 调大就一目了然。正确写法是把第二个的开头竖线去掉:\multicolumn{2}{c|}{right}。这是那种朴素实现的副作用——\multicolumn 只是照你所写的内容替换掉被丢弃的模板,并不会替你处理重复。
纵向合并 — multirow 宏包与 \multirow 的引数
纵向跨行的单元格无法用标准命令创建,需要载入 multirow 宏包(TeX Live 2024 附带的是 2021 年 3 月 15 日的 v2.8)。忘记载入的话,只会得到 ! Undefined control sequence.。完整写法有六个位置——\multirow[vpos]{nrows}[bigstruts]{width}[vmove]{text}——其中只有 nrows、width、text 是必需的:跨越的行数、单元格宽度、内容。宽度传 * 时文本按自然宽度排版,因此 \multirow{3}{*}{Label} 是最直接的形式。
决定性的规则是 让被跨越的下方各行留空。把 \multirow 放在上方行,后续行在该列位置只写 &。用官方文档的说法:该列的其他行必须留空,「否则 \multirow 生成的内容会覆盖它」。若真的在那里写了内容,不会报任何错误——两段文字只会叠印在同一位置。另外,nrows 也可以取负值:正值表示「本行以及下方 n−1 行」,负值表示「本行以及上方 |n|−1 行」,跨越的行数相同——区别只在于把命令写在顶行还是底行。它还允许小数,所以 \multirow{2.5}{*}{…} 可以微调高度。
\usepackage{multirow}
% ...
\begin{tabular}{|l|l|c|}
\hline
\multirow{2}{*}{Fruit} & Apple & 380 \\
& Orange & 120 \\
\hline
\multirow{2}{*}{Vegetable} & Carrot & 90 \\
& Radish & 150 \\
\hline
\end{tabular}| 引数 | 作用 | 备注 |
|---|---|---|
[vpos] | 垂直对齐(可选,最前) | 默认 c;也可用 t / b |
nrows | 跨越行数(必需) | 正值向下,负值向上;允许小数 |
[bigstruts] | 跨越范围内 bigstrut 的总数(可选) | 默认 0;可前置 t / b / tb 做更细控制 |
width | 单元格宽度(必需) | * 为自然宽度,= 取列宽,绝对值则为 parbox |
[vmove] | 位置微调(可选,在 text 之前) | 正值上移,负值下移 |
\multirowsetup | 在 text 之前展开的设置 | 初始值为 \raggedright;可用 renewcommand 修改 |
如果给 width 传绝对长度而非 *,例如 \multirow{2}{4em}{…},内容会作为该宽度的 parbox 排版,长标签因此会换行。用这种写法时,你可能会注意到文字 是左齐而非两端对齐。这不是缺陷而是设计:在 text 之前展开的钩子 \multirowsetup 的初始值就是 \raggedright。想改成居中,写 \renewcommand{\multirowsetup}{\centering} 即可。此外,width 还接受一个值 =,意思是「使用该条目所在列已定义的宽度」——在 p{} 列或 tabularx 的 X 列中很方便,但在没有确定宽度的列里使用,会得到一个异常宽的结果。
\multirow 不会预留空间 — 当文字压到下面各行时
这是 \multirow 最著名的事故。它不会让表格为自己所需的垂直空间预留位置。 盒子的高度只是根据 nrows 和 bigstruts 估算 出来的,因为它并不知道各行的真实高度。所以当内容形成一个很高的盒子时,它就会径直 垂到 下方各行上,或者伸出表格之外。官方文档也坦率地写明,用一张药物副作用表举例:两个 \multirow 条目「太高了,第一个压进第二个条目,第二个则伸出了表格」。只要 nrows 相对于内容分量太小,这个症状就会出现。
有三种修法,其中前两种出自官方文档。第一,添加空行来覆盖重叠部分——缺多少就补写多少行 & & \\。这是最稳妥的路子,只需数清行数。第二,把行本身拉高——在每一行放一个零宽支柱,例如 \newcommand{\mystrut}{\rule[-8pt]{0pt}{20pt}},以换取高度(也可以改用 bigstrut 宏包的 \bigstrut,并在 [bigstruts] 中申报跨越了多少个)。第三,把 nrows 报得比实际更大,甚至用小数——最省事,但容易打乱与线条的关系,所以留作最后手段。
\usepackage{multirow}
% ...
% the label wraps to four lines but the block has only two rows,
% so raise nrows to 4 and add two empty rows to cover the overlap
\begin{tabular}{|l|c|}
\hline
\multirow{4}{4em}{A long label that wraps} & one \\
& two \\
& \\
& \\
\hline
next & row \\
\hline
\end{tabular}同样的弱点——不知道行高——在 表格中某处有异常高的行时 也会露头。文档中的例子是:第三行在 p{4cm} 列里换行变长,于是 \multirow{3}*{Week 38} 落在了视觉中心的上方。当各行高度不齐时,\multirow 估算的中心与眼睛看到的中心就对不上。这类偏移要靠给 [vmove] 一个小长度手工修正。使用 .1ex 这类相对单位,能让修正值在日后改变正文字号时大体依然成立——这也是官方文档的建议。
当线条撞上跨越的单元格 — 用 \cline 和 \cmidrule,而不是 \hline
这是整个话题里最棘手的地方。在 \multirow 跨越的两行之间写 \hline,线条会 径直穿过合并单元格里的文字——标签看上去像被划了删除线。原因是 \hline 只是在两行之间铺一条横贯全表宽度的 \hrule,完全不知道上面压着什么。把 \arrayrulewidth 调粗,就能清楚看到字母被线穿过。对策很简单:把那一列排除在线条之外——用列范围写法,标准 LaTeX 用 \cline{2-3},使用 booktabs 则用 \cmidrule{2-3}。
\usepackage{multirow}
% ...
% \hline here would draw straight through the word "Group";
% \cline{2-3} spares column 1
\begin{tabular}{|l|l|c|}
\hline
\multirow{2}{*}{Group} & alpha & 1 \\
\cline{2-3}
& beta & 2 \\
\hline
\end{tabular}\cline 和 \cmidrule 都能接受列范围,但 性格不同。\cline{i-j} 的粗细是 \arrayrulewidth(默认 0.4pt),并且恰好从指定列的一端伸到另一端,所以相邻的两条 \cline 会连成一条。booktabs 的 \cmidrule{i-j} 更细——粗细为 \cmidrulewidth,默认 0.03em,在 10pt 正文下约 0.3pt——并在上下留出 \aboverulesep 与 \belowrulesep 的空白。实用上更关键的是,圆括号形式的可选引数可以修剪两端:写 \cmidrule(lr){2-3} 会让它左右各缩短一点,从而 与相邻的 \cmidrule 之间留出缝隙,读起来是两条独立的线——这是并排放置多个表头分组时的标准做法。
\usepackage{booktabs}
% ...
% two \multicolumn headers, each underlined by its own trimmed rule
\begin{tabular}{lcccc}
\toprule
& \multicolumn{2}{c}{2024} & \multicolumn{2}{c}{2025} \\
\cmidrule(lr){2-3} \cmidrule(lr){4-5}
Item & Q1 & Q2 & Q1 & Q2 \\
\midrule
Apple & 1 & 2 & 3 & 4 \\
\bottomrule
\end{tabular}这就是 \multicolumn 与线条组合的实用标准形态。\multicolumn{2}{c}{2024} 生成跨两列的表头,正下方的 \cmidrule(lr){2-3} 则表示「这两列属于一组」。去掉 (lr),两条线就会连起来,组与组的分界随之消失。另外,如果确实想在 同一列范围上 叠出两条 \cmidrule,需要在中间加上 booktabs 的 \morecmidrules——因为直接连写两条时,它会把两者当作同一条线所在的那一行。在带底色的表格中,\cline 可能被背景色盖住而消失;该症状及使用 hhline 的规避办法,见「给表格上色」一页。
合并矩形块 — \multirow 要放在 \multicolumn 内部
同时跨越横纵两个方向的 矩形块,要靠 \multirow 与 \multicolumn 的组合来做。顺序是固定的:把 \multirow 放在 \multicolumn 内部。官方文档也以 \multicolumn{2}{c}{\multirow{3}{*}{Multi-multi}} 为例,并明确写道反过来嵌套「will not work」。记住:外层 \multicolumn 负责横向跨度,内层 \multirow 负责纵向跨度。而 \multirow 的铁律在这里同样有效——下方各行的同一位置保留 \multicolumn 的形式,但 内容留空。
\usepackage{multirow}
% ...
\begin{tabular}{|l|l|c|}
\hline
\multicolumn{2}{|c|}{\multirow{2}{*}{Region and class}} & Count \\
\multicolumn{2}{|c|}{} & \\
\hline
East & Kanto & 12 \\
\cline{1-3}
West & Kansai & 9 \\
\hline
\end{tabular}只在一个单元格内换行并不是合并 — makecell
这里要做一个区分。「想让一个单元格内的内容分成多行」并不是合并单元格。 由于 l、c、r 列不会换行,人们很容易想在单元格里直接写 \\——但那样做会 不报任何错误地悄悄毁掉表格:写成 A & B\\C \\,TeX 会把 \\ 读作行末,C 就成了下一行的第 1 个单元格。正因为不报错,这种失败反而更麻烦。如果只是想让短字符串在指定位置换行,makecell 宏包 的 \makecell{…} 最为方便。
\makecell{line1\\line2} 会把内容排成 一个小的一列表格,正因如此 \\ 在其中可以作为强制换行使用。可选参数可指定对齐,例如 \makecell[l]{…}(左齐)、\makecell[t]{…}(顶部),以 垂直和水平对齐各一个字母 传入(默认两者都居中)。用于表头时,还有带统一字体和间距的 \thead{…}。若内容很长、希望按宽度 自动 换行,正确做法是使用 p{width} 列(需垂直居中则用 array 的 m{width}),而不是 \makecell。
\usepackage{makecell}
% ...
\begin{tabular}{lc}
\hline
\thead{Item} & \thead{Year on year\\(\%)} \\
\hline
Revenue & \makecell[r]{+8.2\\(record high)} \\
\hline
\end{tabular}何时该用哪个工具
选择很简单:横向跨列用 \multicolumn,纵向跨行用 \multirow,矩形块两者并用(\multirow 放内层),只想在一个单元格内换行才用 \makecell。如果要让长文本按宽度换行,答案不在其中任何一个——那是 p{width} 列的职责。写完含有合并的表格后,请务必看一眼 PDF,检查三点:是否忘了把 \multirow 下方的单元格留空(有没有文字叠印)、\multirow 的盒子是否垂出了块外(增大 nrows 或补空行),以及 线条是否横切了合并单元格(把 \hline 换成 \cline 或 \cmidrule)。这三点几乎能覆盖实际发生的所有事故。