按如下方式定义一个宏(#2
后面再跟一个#
):
\def\foo#1#2#{*#1,#2*}
经测试,foo会将其接收的第一个参数(#1
)后,紧接着遇到的第一个左花括号“{
”前的内容作为#2
,比如,
然后按如下方式赋值:
\foo 1 2 {} % 会得到 *1,2*
\foo 1{2} % 会得到 *1,*2
请问为何会如此,如何理解?
这个就是 TeX 中的一个特殊规则(见 TeXbook 204 页),在早些年代,计算机内存较小,这种定义方式可以节省一些字节量。
现在定义宏不推荐这种写法,能看明白就行。
用 pdftex
跑下面的代码:
\tracingmacros=1
\def\foo#1#2#{*#1,#2*}
\foo 1 2{}
\foo 1 {2}
\bye
在 log 文件中会看到:
\foo #1#2{->*#1,#2*{
#1<-1
#2<- 2
\foo #1#2{->*#1,#2*{
#1<-1
#2<- %%%注意这里有一个空格,自己测试一下
对于 \foo 1 2{}
,\foo
接收的第一个参数是 1
,第二个参数为第一个参数与左花括号之间的所有 token,也就是空格符和 2
;而对于 \foo 1 {2}
,第一个参数还是 1
,而第二个参数只剩下一个空格符;再改一下,如果删掉 1
后面的空格符的话(即 \foo 1{2}
),第二个参数就是空的。
我们可以通过这个特性定义带可选参数的宏,看看从 xcolor
里面节选的一段代码:
\documentclass{article}
\usepackage{xcolor}
\makeatletter
\def\testclr#1#{\@testclr{#1}}
\def\@testclr#1#2{{\fboxsep\z@\fbox{\colorbox#1{#2}{\phantom{XX}}}}}
\makeatother
\begin{document}
\testclr{red}
\testclr[rgb]{1,0,1}
\end{document}
\testclr
只有一个参数,且这个参数位于宏本身与左花括号之间,所以
\testclr{red}
,#1
为空,于是传给 \@testclr
的第一个参数为空,第二个参数为 red
\testclr[rgb]{1,0,0}
,第一个参数为 [rgb]
,第二个参数为 1,0,0
,于是传给 \@testclr
的两个参数也一样\foo 1 2 {}
得到的是 *1, 2 *
,而不是 *1,2*
。
TeX 宏的参数有两种,一种是没有分隔符的参数(或定界符,undelimited parameter),另一种是有分隔符的参数(delimited parameter)。参数的分隔符就是两个参数之间的记号,是标记参数结束的一些符号。
TeX 在开始读取 undelimited parameter 时,会忽略空格,之后如果发现第一个记号是 begin-group character,则认为这个参数是由一对括号包裹(可以认为 {
标记参数的开始位置,}
标记结束位置,它们不会在最终的参数里出现),否则这个参数就是遇到的第一个非空格记号。
读取 delimited parameter 时,不会忽略空格,这个参数由指定的定界符来标记结束的位置(定界符必须与定义时的完全匹配),并且 {}
需要正确嵌套,并且如果结果是一对正确嵌套的组,则最外层的括号不会出现在实参中。在这两种情况中,{
可以是任意 catcode 为 1 的字符(即 begin-group character),}
可以是任意 catcode 为 2 的字符(即 end-group character)。
macro parameter(catcode=6 的字符或被 let 为这些字符的控制序列,一般是 #
) + begin-group character 可以看成是一个特殊的定界符,只能放在最末尾。在匹配时,begin-group character 必须和定义时使用的字符完全一致。
如假定 [
和 {
的 catcode 都为 1,则 \def\foo#1#[...}
在使用时也必须是 \foo aaa[...
,不能是 \foo aaa{...
。
了解这些之后,答案就很清楚了。\def\foo#1#2#{...}
,第一个参数是 undelimited parameter,第二个参数由 {
定界,#1
是 1
,#2
分别是 2
和空。
需要注意的是,TeX 在读取一个控制词(如 \a
、\foo
是控制词,\;
是控制符,它们都是控制序列)会忽略它后面的空格,所以 \foo␣␣
等于 \foo␣
等于 \foo
。如前所述,macro parameter 可以是字符,也可以是控制序列,如
\let\pp#
\def\foo#1\pp2{\pp1,#2}
\def\foo#1#2{#1,#2}
两种完全一致。
空格只比较 catcode,不比较 charcode,TeX 在读取输入文本时,所有的 catcode=10 的字符都会变成 charcode=32, catcode=10 的记号。
更详细的内容可以参考 The TeXBook 第 20 章。
好的,谢谢大佬解惑!
我原本是在
LaTeX3
的l3prg
模块对函数\prg_set_conditional:Npnn
实现的底层代码中看到这样的定义:第一次见这种定义方法,但又不符合“定界符参数”的用法,觉得新奇,现在看来,确实是有特殊用途啊。