这个问题源于我最近对各种用户定义命令的活动。在最近提出的一个问题的答案%
,@egreg 向我指出了在环境分隔符后不要忘记 a 的重要性\end{minipage}
(另请参阅他的另一个答案),我还注意到,被遗忘的空格 ir<CR><LF>
对两个相邻 -es 的正确放置有巨大 (1ex!) 的影响\parbox
,这两个 -es 出于方便的原因在两行连续的代码中定义。因此,我的问题是:除了 Kopka 和 Daly ([1],§10.5.2 pp. 199-200) 的观察之外,在编写用户定义的命令、代码等时,是否存在一般建议/做法需要遵循(或许需要遵守),以避免导致“奇怪”的格式问题?
[1] Kopka,Helmut,Daly,Patrick W.(2004),“LaTEX 指南",第 4 版,波士顿等:Addison-Wesley,第 xii+597 页。
答案1
这解决了更普遍的 OP 问题,“在编写用户定义命令时,是否存在需要遵循(或可能遵守)的一般建议/做法”?OP 要求我将我的评论转换成答案,所以就这样了。
\relax
关于数字和维度
TeXbook 第 71 页说“如果 [维度] 后面的文本恰好以或开头,则\relax
后面的控制序列可防止 TeX 认为存在关键字。”因此,后面跟着维度可防止意外将后面的内容解释为粘性维度的一部分。以下显示了这个问题。看看输出中有什么不同:.5em
plus
minus
\relax
\relax
\newlength\x
\x=1ex Plus 1pt to your score. \the\x\par
\x=1ex\relax Plus 1pt to your score. \the\x
类似的需要果断终止数字的情况也可能出乎意料地出现,正如 Ryan 对这个问题的回答所表明的那样,在 \ifnum ... \fi 构造之后使用 \relax。下面是一个例子,第二部分显示了带有\relax
,第一部分没有:
\documentclass{article}
\newcount\minleft
\newcount\timehour
\begin{document}
\time=724\relax
\timehour=9
\ifnum\time>720\advance\timehour by-12\fi%\relax
\number\timehour:
\time=724\relax
\timehour=9
\ifnum\time>720\advance\timehour by-12\fi\relax
\number\timehour:
\end{document}
如果没有\relax
,后续代码将被解释为数字计算的一部分。
然而,正如 David 在评论中指出的那样,总是存在例外。他指出,如果数字计算是 的一部分\typeout
,那么\relax
将会明确显示在日志文件中,这可能不是我们所希望的。他建议将 的输出\typeout{\ifnum3=3 yes\else no\fi}
与进行比较\typeout{\ifnum3=3\relax yes\else no\fi}
。
括号[ ]
和可选参数
对于\newcommand
定义,可选参数由括号对分隔[ ]
。与可以以平衡方式有效嵌套的括号组不同,解析\newcommand
不允许嵌套括号组(请参阅\NewDocumentCommand
了解替代方法)。这意味着,如果您正在为用户创建宏,则必须考虑用户是否会将左括号用作其可选参数的一部分,因为这会破坏编译。解决方法是使用括号将与定义关联的可选参数中使用的任何括号分组在一起\newcommand
。
缩进和垂直模式
LaTeX 默认缩进段落。如果您要创建宏,请确保当 TeX 仍处于垂直模式时,宏在段落开头使用时能够按照您希望的方式运行。如果不是,典型的修复方法包括在宏定义的开头合并\noindent
and/or 。\leavevmode
对于下面 MWE 中显示的情况,请注意边注相对于段落第一行的垂直位置。第二种情况下的错误是因为在行\mnote
首调用了,而没有离开垂直模式。
\documentclass{article}
\usepackage{lipsum}
\newcommand\mnote[1]{\marginpar{\footnotesize#1}}
\begin{document}
Here is my margin note\mnote{Hi Mom}. \lipsum[3]
\mnote{Hi Mom}Here is my margin note. \lipsum[3]
\renewcommand\mnote[1]{\leavevmode\marginpar{\footnotesize#1}}
\mnote{Hi Mom}Here is my margin note. \lipsum[3]
\end{document}
答案2
您提到的那些“奇怪”的格式问题实际上一点也不奇怪,只是在习惯用 TeX 编写代码之前很难发现它们。
在这种情况下,规则很简单:
- 当 TeX 读取一个或多个空格时,它会将其视为单个空格。
- 当 TeX 读取一个新行时,它会将其视为空格。
- 当 TeX 读到两行或更多新行时,它会将其视为段落分隔符。
此处的棘手之处在于,%
行尾之前的 会“隐藏” TeX 中的换行符。我对此进行了更详细的解释这里,如果你有耐心。
当然,TeXBook 是一个很好的参考书。特别是,关于 TeX 如何读取输入,请阅读前 7 章。它不到 50 页,包含有关 TeX 工作原理的非常有用的信息。
答案3
出现“奇怪”格式问题的原因是,单个换行符相当于 TeX 的一个空格,即
foo bar
是相同的
foo
bar
如果你真的想排版
foobar
但为了便于阅读,请将输入分成两行(通常应该这样做),您必须通过注释来删除换行符:
foo%
bar
请注意,行首的空格或制表符始终会被忽略,因此您不必担心之前的缩进bar
。
您可能知道,宏会吞噬其后的空格。因此,它们也会吞噬输入中换行符产生的空格,即
\foo bar
是相同的
\foo
bar
\foo
并且在任何一种情况下,和的输出之间都没有空格排版bar
。
在 TeX 读取输入时吞噬空格的其他情况下(例如读取数字时),相同的规则也适用于单个换行符。
至于建议:定义宏时,%
在每个您不明确希望排版为空格且不会被吞噬的换行符前放置一个。如果您不确定后者,请在%
那里放置一个以防万一。这不会造成任何伤害。(见下文。)
正如 campa 在他的评论中指出的那样,在某些情况下,%
行尾的 可能会破坏您的代码,因为 TeX 会继续读取下一行代码中的数字,而该数字应该以您用 注释的行尾结束%
。在这些情况下,您可以改用\relax
。(尽管如果您使用它们,您可能应该确切地知道 TeX 如何读取数字分配和测试。如果您不这样做,您应该改用相应的 LaTeX 宏。)
答案4
以避免使用具有可见效果的“隐形”命令的微妙之处。
要记住的是,TeX 主要是一个排版系统,而不是一种编程语言。默认情况下,一切都是应该产生可见的效果:您在文件中输入内容a+b
,.tex
然后输出结果为“a+b”;TeX 不会尝试对您的“变量”进行任何智能处理。TeX 确实有宏,但它们主要用作输入快捷方式:如果您可以识别出一种模式,则可以将其提取为宏,以便将某些 、#1
等#2
替换到其中,而不是重复输入类似的东西。
你越是偏离这个思维模式,你就会遇到越多意想不到的问题。相反,我发现在定义“命令”(宏)、“环境”(基本上是宏)等时非常有用的一种做法是先写出展开的内容(宏的定义),对其进行全面调试,然后再将其提取到宏中。换行符也类似:首先将所有内容写在一行上,在你确定可以的地方插入空格,然后将一些空格转换为换行符。(通过练习,你不必明确地执行这两件事,但至少养成习惯是好的思维我认为这是你的问题最普遍的答案:
在编写用户定义的命令、代码等时,是否存在需要遵循(或许需要遵守)的一般建议/做法,以避免出现“奇怪”的格式问题?
除此之外,你还需要了解 TeX 如何处理空格(不特定于宏,因为宏只是扩展的):在控制序列之后,它会忽略空格,当它进入“跳过空格”状态时,它会向前跳过(例如,将连续的空格视为 1),TeX 在扫描数字时会做什么,等等。你可以从中学习所有这些TeXbook或者TeX 按主题分类。
以下是此建议如何应用于迄今为止提到的实例的示例。
这回答你的另一个问题来自@egreg:在这里,当您将以下内容作为 的一部分编写时
\newcommand
:\fbox{% \begin{minipage}[t]{\linewidth-2ex} #1 \end{minipage} }
这样写似乎很自然,但是当你把它放在一行时,你会注意到你写了类似
\fbox{x }
when 的内容,而不是你打算 的内容\fbox{x}
。但如果你开始把它写在一行上:\fbox{\begin{minipage} ... \end{minipage}}
那么你很可能一开始就不会引入任何多余的空格。然后当你想插入换行符时,就没有空格可以转换为换行符。所以你唯一能做的就是选择一个地方,然后将其转换为换行符,然后可以
%
随意地加上一些空格。
这您链接到的其他答案:此处,用户写道:
\newcommand\quoteRefFormatAfter[3]{{ \footnotesize \par\smallskip \hfill #1 \par \hfill \quote*{#2} % \par \hfill{\scriptsize#3}% Problem !!! }}
如果定义如下第一的,而且只用一行,
{ \footnotesize \par\smallskip \hfill #1 \par \hfill \quote*{#2} \par \hfill{\scriptsize#3}}
问题更容易被发现:该组以空格开头(将被排版,因为记住 TeX 主要是一个排版程序),s 之前有不需要的空格
\par
,等等。(同样,在原始行中,以空格结尾的行后跟%
没有用的。)所以,如果我们首先将该行更改为仅在可以接受的地方有空格:{\footnotesize \par\smallskip \hfill #1\par \hfill \quote*{#2}\par \hfill{\scriptsize#3}}
然后我们可以通过将空格转换为换行符来插入换行符:
{\footnotesize \par\smallskip \hfill #1\par \hfill \quote*{#2}\par \hfill{\scriptsize#3}}
现在如果我们想要更多换行符,我们必须求助于
%
:{% \footnotesize \par\smallskip \hfill #1\par \hfill \quote*{#2}\par \hfill{\scriptsize#3}% }
我们可以将其作为命令的定义。
另一个问题也是可能的:如果你漏掉一个空格(包括%
在行尾放置以忽略换行符),那么 TeX 可以继续在你可能插入空格的位置寻找数字(或其他任何内容),这可能会导致意外的结果。
总结(重复):TeX 是一个排版程序。不要随意添加空格、(等同于)换行符或%
s。所有这些都可能带来问题。相反:首先仔细地以展开形式写出所有内容,尽可能使用空格。(任何可以接受空格的地方,都放一个空格。)然后根据您的喜好将空格转换为换行符,并使用将非空格转换为换行符%
。