这是一个产生虚假空白的 MWE:
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\newcommand\aetest[2][\relax]{%%
\def\aejunk{<#2>}%%
\ifx\relax#1
\else
\def\aejunk{(#2:#1)}%%
\fi
\fbox{\aejunk\rule[-0.5ex]{0pt}{2.5ex}}%%
}
\begin{document}
\aetest[world]{hello}%%
\aetest{ciao}
\end{document}
我知道如何解决这个问题。我只需要\ifx\relax#1
重写
\ifx\relax#1%%
但是我本以为,由于这个测试只有在 时才为真#1
,\relax
所以后面的空格会被吸收,行尾注释应该是不必要的。我猜想我对标记化过程有些不理解。有人能解释一下这里发生了什么,导致空格悄悄出现吗?
答案1
当 TeX 吸收定义的替换文本时,它不会进行任何扩展(除非你使用\edef
或\xdef
)。在你的替换文本中,你有
\ifx\relax#1
行尾算作空格,因为它不在控制字后面,因此在标记化过程中会被转换为空格。在使用宏时,不会在扩展过程中忽略空格,因为 TeX 不会查找<one optional space>
,它会在数字常量之后或在赋值之后查找 ,=
其中=
是可选的,还有一些其他情况,甚至会忽略整个空格标记字符串。
#1
在“无可选参数”的情况下,用 替换 的事实\relax
完全无关紧要,因为只有在标记化过程中,控制字后的空格才会被忽略:空格代币控制字之后潜入的那些内容不会被忽略。
还要注意,在你进行的定义中,TeX 实际上执行了两个\def
命令。首先它执行
\expandafter\def\expandafter\aetest\expandafter{%
\expandafter\@protected@testopt
\expandafter\aetest
\csname\string\aetest\endcsname{\relax}%
}
进而
\expandafter\def\csname\string\aetest\endcsname[#1]#2{%
\def\aejunk{<#2>}%%
\ifx\relax#1
\else
\def\aejunk{(#2:#1)}%%
\fi
\fbox{\aejunk\rule[-0.5ex]{0pt}{2.5ex}}%%
}
在第二个定义中,没有“无可选参数”的#1
情况\relax
。但是,正如我所说,这从一开始就无关紧要。
如果您想要对“无可选参数”进行安全测试,请使用xparse
:
\usepackage{xparse}
\NewDocumentCommand{\aetest}{om}{%
\IfNoValueTF{#1}
{\def\aejunk{<#2>}}
{\def\aejunk{(#1:#2)}}%
\fbox{\aejunk\rule[-0.5ex]{0pt}{2.5ex}}%
}
所以\aetest{ciao}
会\aetest[]{ciao}
产生不同的结果。
另一方面,如果你只是想测试可选参数是否未提供或为空,通常
\if\relax\detokenize{#1}\relax
測試更好。
答案2
好的,我想我知道发生了什么。
当我定义\aetest
\newcommand\aetest[2][\relax]{%%
\def\aejunk{<#2>}%%
\ifx\relax#1
\else
\def\aejunk{(#2:#1)}%%
\fi
\fbox{\aejunk\rule[-0.5ex]{0pt}{2.5ex}}%%
}
目前,还没有扩展#1
或#2
尚未发生。因此,当\aetest
定义时,行尾字符#1
将被标记化。因为一旦空格被标记化,它就不会在以后消失:例如当
\aejunk{ciao}
得到扩大。