当你写作时
\edef\a{\b}
\let\a\b
结果通常相同。这两个命令有什么区别?它们什么时候表现相同?什么时候表现不同?
(这个问题不是为我而问的;灵感来自这个问题比较\let
和\def
,我问这个问题是为了将来可以作为参考,指导人们。)
答案1
\edef
扩展了参数,而\let
没有。下面是一个例子来说明区别:
\def\txt{a}
\def\foo{\txt}
\let\bar\foo
\bar -> a
\def\txt{b}
\bar -> b
然而,
\def\txt{a}
\def\foo{\txt}
\edef\bar{\foo}
\bar -> a
\def\txt{b}
\bar -> a
还有其他不同,比如参数等等。但如何扩展可能是最重要的(?)。
这是一个有趣的问题。我可以进一步扩展这个问题吗?
\let
和之间有什么区别\expandafter\def\expandafter\foo\expandafter
?它们的行为总是一样的吗?
\def\txt{bar}
\def\foo{\txt}
\expandafter\def\expandafter\bar\expandafter{\foo}
\let\BAR\foo
{\tt \string\bar = \meaning\bar}\par
{\tt \string\BAR = \meaning\bar}
对这个反问句的隐晦回答,因为最初的问题是我写的:)
如果您对不带参数的宏执行此操作,则它们之间的差异可以忽略不计。另一方面,
\expandafter\def...
如果您尝试复制带参数的宏的定义,则不能使用该构造。事实上,这揭示了我希望人们在这里讨论的一个方面。
\let
在执行宏的瞬间创建宏的文字副本,而\edef
将获取宏的内容并递归扩展它以完全创建一个新的宏。当您仅将宏用作存储数据的位置(例如\def\name{Will}
)时,差异基本上无关紧要,但是当将宏用作接受参数或具有对其应用了各种扩展限制的内容的“函数”(使用\protected
、\noexpand
等等)时,差异确实非常重要。
答案2
正如已经说过的,\edef
扩展了它的参数。这不仅意味着内容将不同于带有的定义\let
,还意味着参数必须是可扩展的。你总是可以做\let\a\b
但例如\edef\a{\tableofcontents}
会中断。还\edef
扩展了它的参数,它不会执行它们。所以像这样的事情不会起作用:
\def\my{\def\myinner{c}}
\edef\test{\my}
答案3
主要区别在于,它\let
创建了对现有值的新引用(即名称),而\def
(和朋友)则创建了全新的值和名称对。如果您能将名称和值视为独立的东西,那么这两个原语之间的区别就相当容易理解了(在我看来)。
答案4
除了其他答案之外,让我阐明这个问题和其他问题的一个关键点答案隐含地显示。
假设\b
是一个无参数的宏,其展开可能包含\c
,比如:
\def\c{Alice}
\def\b{Hello \c !}
我们必须区分(一步式)扩张和递归扩展:\def\a{\b}
扩展后\a
给出标记列表\b
,但递归的扩展\a
与递归的扩展\b
(此处为Hello Alice!
)。由于我们处理的上下文不会递归扩展其参数(与段落主体不同),因此区别很重要。
然后:
\def\a{\b}
使得\a
的展开式等于\b
,因此重新定义\b
会影响 的递归展开式\a
。\edef\a{\b}
使\a
的展开等于\b
的递归扩展,即通常* 非宏标记列表 — 如果是这样,则重新定义不会影响的(递归)扩展\a
。\let\a\b
使\a
的展开式等于\b
的展开式,因此重新定义\b
不会影响\a
,就像\edef
(例子)。但影响\b
扩展的任何事情(例如,重新定义\c
)仍然会产生影响\a
— 与 不同\edef
。
*编辑:@egreg 指出这\noexpand
是一个例外 — 它允许递归扩展包含未扩展的宏。详情见https://tex.stackexchange.com/a/519/1340。