以下内容引自Peter Grill 的回答有疑问LaTeX 动态宏定义。
是
\expandafter
必需的,因为\newcommand
需要在之后处理\csname
。
作为 TeX 编程的新手,如何知道我是否需要\expandafter
?
答案1
让我们看一下latex.ltx
(LaTeX 内核)的三个例子。
\@namedef
\def\@namedef#1{\expandafter\def\csname #1\endcsname}
这完全类似于\expandafter\newcommand
这是问题的主要内容。\@namedef{foo}
我们得到
\expandafter\def\csname foo\endcsname
并且控制序列\foo
是在\def
开始起作用之前构建的: 的扩展\csname
是符号标记,其名称是其后的内容,直到匹配\endcsname
(完全扩展,但分析这一点会走得太远)。
\loop
\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}%
\iterate \let\iterate\relax}
我们以以下形式使用它
\loop
<code A>
\ifz<condition>
<code B>
\repeat
其中\ifz
是 TeX 条件之一,<condition>
是测试,如果为真,则循环继续。这就变成了
\def\iterate{<code A>\ifz<condition><code B>\relax\expandafter\iterate\fi}
\iterate \let\iterate\relax
第一行是一个赋值:它被执行并从输入标记列表中删除,留下
\iterate \let\iterate\relax
现在\iterate
展开,所以我们得到
<code A>\ifz<condition><code B>\relax\expandafter\iterate\fi
\let\iterate\relax
TeX 扩展/执行<code A>
,然后评估条件。如果条件为假,则\fi
跳过之前的所有内容,因此剩下的是
\fi\let\iterate\relax
的展开式\fi
为空;执行下一个赋值,结束循环。如果为真,则我们得到
<code B>\relax\expandafter\iterate\fi
\let\iterate\relax
现在<code B>
被扩展/执行;\relax
什么也不做,我们就有了魔法
\expandafter\iterate\fi
\let\iterate\relax
这里\expandafter
扩展了\fi
,它有空的扩展:所以 TeX 发现
\iterate \let\iterate\relax
并重新启动。如果\expandafter
省略,则\fi
令牌会累积,可能会在长循环中填满内存。
\@onlypreamble
内核中的几个命令按照以下方案定义
\def\@foo{...}
\@onlypreamble\@foo
这意味着\@foo
不能在 之后使用\begin{document}
,否则会触发“只能在前导码中使用”错误。让我们看看\@onlypreamble
会发生什么:
\def\@preamblecmds{}
\def\@onlypreamble#1{%
\expandafter\gdef\expandafter\@preamblecmds\expandafter{%
\@preamblecmds\do#1}}
第一个\@preamblecmds
初始化为空。如果我们说
\@onlypreamble\@foo
我们得到
\expandafter\gdef\expandafter\@preamblecmds\expandafter{\@preamblecmds\do\@foo}}
第一个\expandafter
扩展了第二个,第二个扩展了第三个;最后一个又扩展了\@preamblecmds
。如果这是宏的第一次调用\@onlypreamble
,我们会得到
\gdef\@preamblecmds{\do\@foo}
为了让问题更有趣,我们现在调用
\@onlypreamble\@bar
我们再次得到
\expandafter\gdef\expandafter\@preamblecmds\expandafter{\@preamblecmds\do\@bar}}
但现在的扩展\@preamblecmds
是\do\@foo
:所以最后我们得到
\gdef\@preamblecmds{\do\@foo\do\@bar}
每次调用 时,都是如此\@preamblecmds
。当 LaTeX 运行时,\begin{document}
它会说
\gdef\do#1{\global\let #1\@notprerr}\@preamblecmds
该\do
命令被定义为使其参数等同于\@notprerr
(触发上述错误);然后\@preamblecmds
被扩展,以便
\do\@foo\do\@bar
(以及许多其他相同类型的令牌)将被执行。
在\@onlypreamble
需要获取替换文本中\expandafter
的扩展\@preamblecmds
前重新定义\@preamblecmds
:做
\gdef\@preamblecmds{\@preamblecmds\do\@foo}
会导致灾难,因为最终的替换文本\@preamblecmds
将是
\@preamblecmds\do\@foo\do\@bar
(还有许多其他组合)。当执行\do...
扩展时,TeX 会查找并扩展它,找到要扩展的内容,找到...\@preamblecmds
\@preamblecmds
\@preamblecmds
无限循环。哎呀!
\@ifundefined
LaTeX 内核提供\@ifundefined
检查具有其参数名称的控制序列是否定义(实际上,未定义意味着真正未定义或等同于\relax
)。
\def\@ifundefined#1{%
\expandafter\ifx\csname#1\endcsname\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
和在网站的其他地方有解释;这里的应用与第一个类似:用法是\expandafter\@firstoftwo
并且在开始行动之前构建控制序列令牌。\expandafter\@secondoftwo
\expandafter
\@ifundefined{foo}{true}{false}
\foo
\ifx
我想在这里强调的是,一般\expandafter\ifx
使用错误的:例如,之后
\def\foo{foo}
考试
\expandafter\ifx\foo\foo
将会回归错误的. 因此,\expandafter
除非你知道有必要。
答案2
对此的简短回答是,\expandafter
每当您需要在前一个标记之前扩展某些内容时,您都需要(我知道这很有用!)。在 Peter 的示例中,代码是
\expandafter\newcommand \csname name#1\endcsname{\emph{#2}}
(其中#1
和#2
是周围宏的参数)。你之所以想要\expandafter
在这里,是因为整个\csname
构造不是本身一个控制序列,但\newcommand
期望它后面的第一个是命名新命令的控制序列。如果没有\expandafter
,它将抓取\csname
自身作为新命令的名称,然后它会出错,因为它实际上并非未定义。
这里的理念是, 是\csname
使用 的“准备” \newcommand
, 本身\newcommand
只有在后续输入得到适当准备后才能有意义地扩展。 的目的\expandafter
是保留它,同时执行某些后续指令,将实际输入转换为所需的输入。
答案3
不是为了与@egreg的答案竞争,而是为了回答你关于何时的评论不是使用\expandafter
。
考虑
\def\foo{hello}
定义\foo
为 hello。所以这是安全的,而且很好。
\expandafter\def\foo{hello}
可能并没有错(但也可能错,而且经常错)。
\foo
如果是尚未定义的常见情况,那么您将收到错误
! Undefined control sequence.
l.1 \expandafter\def\foo
{hello}
因为 expandafter 会尝试\foo
在定义之前进行扩展。
另一方面,如果\foo
已经有了定义\def\foo{world}
,那么
\expandafter\def\foo{hello}
会出现错误
! Missing control sequence inserted.
<inserted text>
\inaccessible
<to be read again>
w
\foo ->w
orld
l.3 \expandafter\def\foo
{hello}
因为它相当于\def world{hello}
最后,如果\foo
已经被定义,\def\foo{\bar}
那么您将不会收到任何错误,但它不会定义\bar
什么 ,如果这是预期的结果,那么这很好,但如果您只是添加“为了好运”,\foo
则可能不是预期的结果。\expandafter
\def\foo{\bar}
\expandafter\def\foo{hello}
\show\foo
\show\bar
生产
> \foo=macro:
->\bar .
l.5 \show\foo
?
> \bar=macro:
->hello.
l.6 \show\bar