结构、标题与目录

\section 真正做的事情不是把字变大,而是声明一个整数——标题的层级。LaTeX 提供七级标题,从 \part\subparagraph,编号从 −1 到 5;随之而来的两个问题——这个标题要不要编号(secnumdepth)、要不要列进目录(tocdepth)——不过是与这个整数做大小比较而已。层级一旦定下,编号和目录也就基本定了。本页依次讲七个层级的取舍、星号形式、\tableofcontents 为何需要编译第二次,以及 \appendixbook 的三段结构如何改写底层的编号。

七个标题层级与 level 值

从高到低依次是 \part\chapter\section\subsection\subsubsection\paragraph\subparagraph。这里的「level 值」不是比喻。翻开标准文档类的源码就会看到 \section 定义为 \@startsection{section}{1}{…},第二个参数 1 就是 level 值。\subsection2\subsubsection3\paragraph4\subparagraph5。内核里决定是否输出编号的代码只有一行 \ifnum #2>\c@secnumdepth,其中的 #2 正是这个数字。标题不是让字变粗的装饰,而是告诉读者当前所处论述位置的路标,同时也是决定全篇编号体系的声明。

命令Level 值单位可用类
\part−1(book/report), 0(article)所有类
\chapter0仅 book / report
\section1所有类
\subsection2小节所有类
\subsubsection3小小节所有类(book/report 中默认无编号)
\paragraph4行内段落标题(正文接在同一行)所有类(默认无编号)
\subparagraph5行内小段落标题(正文接在同一行)所有类(默认无编号)

表格最下面两行之所以表现不同,是因为整个视觉设计被压缩成了一个符号。\@startsection 的第五个参数表示「标题之后留多少空白」:\section2.3ex,为正;\paragraph\subparagraph-1em,为。内核读到这个符号——正号表示标题之后换行,负号表示不换行,而是插入相应宽度的水平空白后让正文继续。于是 \paragraph{要点} 会排成「要点 正文从这里继续」,成为流入正文的行内标题(run-in heading)。想在段落开头放一个短标签时用它;想要独占一行的标题,就停在 \subsubsection

latex
\documentclass{report}   % \chapter is available here
\begin{document}
\chapter{Introduction}
\section{Background}
\subsection{Related work}
\paragraph{Key point}The body text continues on this same line.
\end{document}

为什么 \chapter 只存在于 book 和 report

article 类里没有 \chapter。它根本没有被定义,所以在 article 正文中写它会以 ! Undefined control sequence. 中断。这不是缺了某个宏包,而是这个类的设计里根本没有「章」这个单位。因此 article 的最高级标题通常是 \section;需要分章时就改用 report(有章、有摘要)或 book(有章、默认双面、含前置与后置部分)。\part 在所有类中都可用,但地位不同:在 article 中是 level 0,在 bookreport 中是位于 \chapter 之上的 level −1。

跳过层级,编号会把痕迹留下来。在 report 中把 \subsection 直接放在 \chapter 之后,标题编号会变成 1.0.1。中间的 0 就是从未递增过的节计数器。LaTeX 不会替你悄悄补上缺口,而是照实打印被跳过那一级的值。反过来,在 reportbook 中一次也没写 \chapter 就开始 \section,编号会从 0.1 开始。编号里出现陌生的 0 时,首先怀疑跳级。

去掉编号:星号形式与 secnumdepth

整篇文档编号到哪一级,由 secnumdepth 这一个计数器决定。它的值是「仍会获得编号的最底层 level 值」,在 TeX Live 2024 附带的标准类中是 article 为 3,bookreport 为 2。所以 article 会给小小节(level 3)编号,而 book 到小节(level 2)为止。想让 book 的小小节也编号,就在导言区写 \setcounter{secnumdepth}{3}。反过来,\setcounter{secnumdepth}{0} 会一次性去掉节及以下的编号,写 -2 则所有标题都不编号。

只想去掉某一处的编号,就用命令的星号形式。写 \section*{Acknowledgements} 得到的标题既没有编号,也不会进入目录。这两件事捆绑在一起,是因为内核中处理星号形式的宏根本不写目录条目。对于「引言」「致谢」「结论」这类带编号显得别扭的标题,这正是想要的效果;如果仍想让它出现在目录里,就在标题之后紧跟 \addcontentsline{toc}{section}{Acknowledgements}。星号标题也不会推进计数器,因此后面的编号不会错位。

另一个值得记住的是可选参数。写成 \section[短标题]{正文中显示的长标题} 时,正文标题用长的,目录和页眉用方括号里的短的。论文的节标题常常一行放不下,这就是保住目录与页眉版面的实用退路。省略方括号时,长标题会同时用于三处。

latex
\setcounter{secnumdepth}{3}          % number subsubsections too
\section{Results and discussion}      % numbered, e.g. "3 Results and discussion"
\section*{Acknowledgements}           % no number, and no TOC entry
\addcontentsline{toc}{section}{Acknowledgements}   % put it back in the TOC
\section[Method]{A detailed formulation of the proposed method}

为什么 \tableofcontents 需要编译第二次

目录只要写下 \tableofcontents 就会出现——但不会在写下它的那一次编译中出现。TeX 从头到尾只读一遍文档,因此排版目录的那一刻,它并不知道后面还有哪些标题。于是 LaTeX 每遇到一个标题,就往辅助文件 .toc 中写入一行形如 \contentsline{section}{\numberline {1}First}{1}{} 的记录,并在下一次运行开头把它读回来排出目录。所以至少需要编译两次。麻烦之处在于,第一次运行只在日志里留下 No file document.toc. 一行,并没有真正的警告。目录空白或过期就这样被交了出去。latexmk 之类的构建工具会自动重复所需次数,从结构上消除这个问题。

图表目录用的是同一套机制:\listoffigures 经由 .lof\listoftables 经由 .lot,同样需要多次编译。要加入不会自动列出的条目,使用 \addcontentsline{ext}{unit}{text}:第一个参数 ext 是目标扩展名(tocloflot),第二个参数 unit 是条目种类(sectionchapterfigure 等),第三个参数 text 是要列出的文字。若要插入根本不是条目的素材——例如标题组之间的空白——则用 \addtocontents{ext}{text},它会把内容原样写进辅助文件,因此像 \vspace 这样的命令要加 \protect

latex
\tableofcontents
\setcounter{tocdepth}{2}                       % list down to subsections

\section*{Introduction}                        % starred: never listed
\addcontentsline{toc}{section}{Introduction}   % list it by hand

\listoffigures                                % goes through the .lof file
\addtocontents{lof}{\protect\vspace{2ex}}      % extra space inside that list

tocdepthsecnumdepth 的区别

目录列到哪一级由 tocdepth 决定。它的取值与 secnumdepth 一样是 level 值,默认也相同——article 为 3,bookreport 为 2——但二者相互独立,所以「编号只到节、目录列到小节」是很常见的设定。决定性的区别在于它们何时生效secnumdepth 在排版当场决定是否输出编号;tocdepth 不是这样:LaTeX 会把所有标题不分深浅地写进 .toc 文件,tocdepth 只是在读回该文件时对这些行做筛选。这一点看得见——默认 article.toc 里确实有 \paragraph 那一行,只是没被打印出来。这种设计有实际好处:调深 tocdepth 不需要重新生成 .toc,因此只要再编译一次就够了

\appendix 与 book 的三段结构

在附录开始的位置只写一次 \appendix。它不是创建标题的命令,而是切换编号方式的开关。在 article 中,它把节与小节计数器归零,并把 \thesection 改成字母显示。在 bookreport 中,它把章与节计数器归零、把 \thechapter 改为字母,并把章标题前的词从 Chapter 换成 Appendix。因此 \appendix 之后照旧写 \chapter{符号表},就会得到「Appendix A 符号表」。若写两次,或在每个附录标题前都写一次,计数器每次都会重置——只放一次即可。

book 类还提供了把书分成三段的命令。\frontmatter 进入前置部分(序言、目录等):页码改为小写罗马数字并从 1 重新计数,同时停止章编号(章标题仍会进入目录)。\mainmatter 进入正文:页码换回阿拉伯数字并再次从 1 开始,章编号恢复。\backmatter 标记后置部分(参考文献、索引等):页码继续累计不变,只是再次停止章编号。要点在于这种不对称——只有 \frontmatter\mainmatter 会重置页码计数器。

latex
\documentclass{book}
\begin{document}
\frontmatter            % i, ii, ... and unnumbered chapters
\tableofcontents
\mainmatter             % 1, 2, ... and numbered chapters again
\chapter{Introduction}
\appendix               % chapters now read "Appendix A", "Appendix B"
\chapter{Notation}
\backmatter             % bibliography, index: page numbers keep running
\end{document}

顺带一提,这整套标题命令的设计者是 Leslie Lamport。他获得 2013 年 ACM 图灵奖,却不是因为 LaTeX,而是因为分布式系统方面的工作——逻辑时钟、安全性与活性、复制状态机等概念。全世界理工科学生每天敲下的 \section,在获奖理由里一个字也没有出现。

编号或目录不对时怎么查

  • 编号里混进 0(如 1.0.1 — 跳级了。检查 reportbook 中是否把 \subsection 直接放在 \chapter 之后,或在任何 \chapter 之前就开始了 \section
  • 目录为空或过期 — 你看到的 .toc 是上一次运行写出的。编译两次。因为没有警告,最可靠的办法是交给 latexmk
  • \chapter 处报 ! Undefined control sequence. — 文档用的是 article。改用 reportbook,或把 \section 当作最高层级。
  • 星号标题不在目录中 — 这是设计使然。在标题之后紧跟 \addcontentsline{toc}{section}{…}
  • 目录里没有小节 — 只是 tocdepth 太浅。设为 \setcounter{tocdepth}{2} 后再编译一次即可,.toc 无需重新生成。
  • 附录编号与预期不符\appendix 是开关而非标题。只在第一个附录标题之前放一次。