当你一遍又一遍输入同样写法,或知道以后想一次性修改所有地方时,就该定义自己的命令(宏)。基础是 \newcommand。本页会依次介绍自定义命令的基础:如何接收参数、如何重新定义既有命令、现代的 \NewDocumentCommand,以及避免“fragile 命令”的写法。
用 \newcommand 定义命令
最基本的工具是 \newcommand{\name}{definition}。第一个参数是要创建的命令名,第二个参数是它代表的内容。比如研究组名称会反复出现,就在导言区定义一次;之后正文中只要输入 \name,就会展开为 definition。
% プリアンブルで定義 / define in the preamble
\newcommand{\grp}{Knuth Typesetting Lab}
% 本文で使う / use it in the body
Welcome to the \grp. The \grp{} was founded in 1978.请注意第二个 \grp 后面的 {}。像 \grp 这样只由字母组成的命令,会把紧随其后的空格当作命令名结束标记吃掉;因此写 \grp was 时会丢失空格,排出类似 “Labwas” 而不是 “Lab was”。标准做法是放一对空花括号 {} 来标记命令名结束位置(详见“语法规则”)。
一条重要原则是:\newcommand 只有在该名称尚未定义时才会成功。若用于已经存在的命令名(包括 LaTeX 内核或宏包命令),会以 Command \name already defined 错误停止。这是有意的安全网,防止静默覆盖既有命令。若要有意改写既有命令,请使用后述的 \renewcommand。
带参数的宏
如果每次调用时内容需要变化,就给命令加参数。像 \newcommand{\name}[⟨nargs⟩]{... #1 #2 ...} 这样,在命令名后的方括号中写 参数个数,并在定义中用 #1、#2 等引用。参数从 #1 到 #9,最多 9 个。
\newcommand{\unit}[2]{#1\,\mathrm{#2}}
% \unit{9.8}{m/s^2} → 9.8 m/s^2(数と単位の間に細い空き)
$a = \unit{9.8}{m/s^2}$也可以 把第一个参数设为可选,并给出默认值。写成 \newcommand{\name}[⟨nargs⟩][⟨default⟩]{...},也就是两层方括号时,#1 会成为可选参数,默认值为 ⟨default⟩。调用时写 \name(使用默认值)或 \name[x](把 #1 设为 x);剩余必需参数从 #2 开始。
% 全 2 引数、うち #1 は省略可能で既定値 2
\newcommand{\plusbinomial}[2][2]{(x + y)^{#1}_{#2}}
$\plusbinomial{n}$ % → (x + y)^2_n (#1 は既定の 2)
$\plusbinomial[3]{n}$ % → (x + y)^3_n (#1 を 3 に)需要注意方括号的计数方式。[⟨nargs⟩] 中的 ⟨nargs⟩ 是 包含可选参数在内的总参数数。所以上例表示“两个参数,其中第一个可选”。另外,不写 [⟨default⟩] 与写空括号 [] 是不同的;后者表示“默认值为空字符串的可选参数”。
重新定义、条件定义与 robust 命令
\newcommand 有三个用途不同的同族命令。它们的参数写法([⟨nargs⟩][⟨default⟩])都与 \newcommand 相同,区别在于遇到已经定义的名称时如何处理。
| 命令 | 遇到已有名称时 | 主要用途 |
|---|---|---|
\newcommand | 报错停止 | 安全地创建新命令 |
\renewcommand | 覆盖它(未定义则报错) | 改写既有命令 |
\providecommand | 什么也不做(保留旧定义) | 可能被重复载入的样式文件 |
\DeclareRobustCommand | 覆盖并写入日志记录 | 可动参数中的 robust 命令 |
\renewcommand 用来重新定义已有命令。它与 \newcommand 正好相反:目标 必须已经定义,否则会报错。比如更改列表项目符号等,替换 LaTeX 预先提供的命令内容时使用它。
% itemize の第 1 階層の記号をダッシュに
\renewcommand{\labelitemi}{--}
% 既存マクロを新しい中身で作り直す
\renewcommand{\grp}{Lamport Typesetting Lab}\providecommand 表示“仅在尚不存在时定义”。如果名称未定义,它的行为与 \newcommand 完全相同;如果名称已经定义,则 什么也不做,保留已有定义(也不报错)。它适合在可能被多处载入的样式文件中“以防万一提供一个命令”。
\DeclareRobustCommand 会创建 robust 命令,即使其内部含有 fragile 代码(这些术语见下一节)。与 \newcommand 不同,它遇到已有名称也不会报错;重新定义时只是在 日志文件中留下记录。不过这样创建的命令略低效,因此只应在内容 fragile 且会用于可动参数时使用;其他情况仍推荐 \newcommand。
fragile 命令与 \protect
LaTeX 中有一种特殊上下文称为 可动参数(moving argument)。\section{...} 中的标题不只在正文中排版,还会 写入目录、页眉和 .aux 辅助文件,移动到多个地方。\caption{...}(图表标题)、\thanks{...},以及 tabular/array 的 @{...} 内容也是可动参数。
在这些上下文中一经展开就会变成无效 TeX 代码的命令称为 fragile 命令,能安全通过的称为 robust 命令。经典处理是在 fragile 命令前放 \protect,告诉 LaTeX “这里不要展开它,原样写出去”。保护是逐命令的:每个 \protect 只保护紧随其后的一个命令。
% 可動引数の中で壊れやすい \verb を保護する
\section{The \protect\verb|\par| primitive}不过有个好消息:自 2019 年 10 月的 LaTeX 起,许多曾经 fragile 的命令已变得 robust。带可选参数的 \raisebox 等命令,以及行内数学 \(...\),如今可以不加 \protect 写入标题。常见仍有问题的是 \verb,它往往连 \protect 也救不了;在标题或图注中最好改写成 \texttt{...}。自己定义宏时,如果一开始就用 \DeclareRobustCommand 定义为 robust,就不必担心 \protect。
现代的 \NewDocumentCommand
现代 LaTeX 还提供另一种更强大的定义法:\NewDocumentCommand{\name}{⟨arg-spec⟩}{...}。它原本是 xparse 宏包功能,但 现在已并入 LaTeX 内核(ltcmd),无需 \usepackage 即可使用。它不写参数个数,而是用 列出参数类型的“参数指定(arg-spec)” 来声明。
| 指定符 | 含义 | 定义内如何接收 |
|---|---|---|
m | 必需参数(mandatory) | 普通的 #1 等参数 |
o | 可选参数 [...] | 未给出时为表示“无值”的标记 |
O{default} | 带默认值的可选参数 | 未给出时为 default |
s | 星号 * 的有无 | 用 \IfBooleanTF 判定真假 |
它比 \newcommand 更强大,原因有两个:可以 接收多个可选参数,并能正规地处理 星号(starred)变体。下面例子用 \IfBooleanTF 根据星号分支(这里 #1 是星号,#2 是必需参数)。写新代码时,推荐使用这个现代接口;详见“xparse”页面。
\NewDocumentCommand{\diff}{s m}{%
\IfBooleanTF{#1}%
{\frac{\mathrm{d}}{\mathrm{d}#2}}% 星つき: d/dx 形
{\mathrm{d}#2}% 星なし: dx 形
}
$\diff{x}$ % → dx
$\diff*{x}$ % → d/dx良好的宏写法
宏最能发挥作用的场景是 语义宏。给事物按 “它是什么” 命名,例如把实数集定义为 \newcommand{\R}{\mathbb{R}},把微分 d 定义为 \newcommand{\dd}{\mathrm{d}},可以让整篇文档记号统一;若以后想改变外观,只需改定义的一行,所有出现处都会更新。
\newcommand{\R}{\mathbb{R}}
\newcommand{\dd}{\mathrm{d}}
% 統一された表記で書ける / write with consistent notation
$\int_{\R} f(x)\,\dd x$不过宏太多也会降低可读性。像 \newcommand{\x}{\xi} 这种极端缩写,几个月后对自己或合作者来说可能就像密码。请把宏限制在 重复很多、可能需要批量修改、或值得按含义命名 的对象上;其他内容直接写出来往往更易读。
最后要避免名称冲突。不要轻易用 \renewcommand 覆盖 LaTeX 内核或宏包命令,副作用可能出乎意料。自己的命令避免过短名称,最好加项目专用 前缀(如 \myR、\bookTitle),这样更不容易与已有命令或其他宏包冲突。拿不准时先用 \newcommand 定义;若出现 already defined 错误,就说明该名称已经被占用。