如何知道我们是否需要 \expandafter?

如何知道我们是否需要 \expandafter?

以下内容引自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

相关内容