1 区别

def和edef都可以定义宏,其区别是:def定义宏时,其宏体不做展开操作;而edef定义宏时,其宏体需要进行展开操作,直到不能再展开为止。def定义的宏,在实际调用时才进行展开,因此宏定义时可以引用还没有定义的其他的宏;edef定义宏时,由于马上就进行展开操作,因此不能引用还没有定义的宏。无论def定义的宏还是edef定义的宏,在实际调用时都会进行“彻底”的展开操作。 既然宏最终还是要展开的,在定义时展开(edef)和在调用时展开(两种情况下都是)到底有什么区别呢?首先看一下def的常见用法(*为TeX提示符,删除了部分不影响理解的输出):
*\tracingall

*\def\b{b}

*\def\c{\b}

*\def\a{\b\c}

*\a

\a ->\b \c

\b ->b

\c ->\b

\b ->b%输出bb

*\def\b{foo}

*\a

\a ->\b \c

\b ->foo

\c ->\b

\b ->foo%输出foofoo
可见,宏\a 的定义是\b\c ,每次执行\a ,其内容会随\b 或者\c 的不同定义而发生变化。再观察一下edef的常见用法:
*\tracingall

*\def\b{b}

*\def\c{\b}

*\edef\a{\b\c}

{\edef}

\b ->b

\c ->\b

\b ->b

*\a

\a ->bb%输出bb

*\def\b{foo}

*\a

\a ->bb%输出bb
可见,当使用edef定义宏\a 时,在定义时\b\c就展开成为了bb,即宏\a成为固定不变的字符串bb,不再随\b或者\c发生变化。

2 noexpand配合edef的用法

既然edef定义宏时就要展开,如何设置能够禁止某些特定的宏在edef中展开呢?办法有很多[1, p216],最常规和最简单的做法是使用\noexpand命令,比如:
\def\a{a}

\def\b{b}

\def\c{c}

\edef\foo{\a\noexpand\b\c}%\foo此时为a\b c

\foo%a\b c继续展开为abc

\def\b{bar}

\foo%输出为:afooc
可以看出,使用\noexpand 禁止某些宏展开,保证了edef所定义的宏具有一定的灵活性。在本例中,\foo 的输出内容还是可以跟随\b发生变化的。如果不使用\noexpand\b 的话,\foo 就成为固定不变的‘abc’了。

3 expandafter配合def的用法

2中的情形相反,有的时候需要在def定义的宏中马上展开一个宏,怎么办?如果大量的宏需要展开,直接切换edef是良策;如果只有个别的宏需要展开,可以首先展开这个宏,然后把展开的宏“拼接”进去,如下所示:
\def\a{a}

\def\b{b}

\def\c{c}

\toks0=\expandafter{\b}

\def\foo{\a\the\toks0\b\c}

4 权衡

使用def还是edef来定义宏,取决于宏定义时需要展开多少宏,以及为什么要展开宏。展开某些宏的目的是让宏的定义“固化”,防止在宏定义后“不经意”或者“有意”的修改(侵犯),提高了宏的安全性。

5 edef和let

edef展开宏定义,let不展开宏定义,如下的指令执行序列可以证明\let不会展开:
*\tracingall

*\def\b{\c\d}

*\def\c{c}

*\def\d{d}

*\let\a=\b

*\a%调用宏\a,输出cd

\a ->\c \d

\c ->c

\d ->d
可以看出,调用\a时展开了\b\c从而输出“cd”。let操作时,\a仅仅复制了\b的内容(tokens),即\c\d,并没有展开\c\d。 TeX中似乎没有\redef命令,因此无法验证重新给\c或者\d赋值时的情形。

引用

1D.E.Knuth, “the TeXbook” (1986). 选自:http://softlab.sdut.edu.cn/blog/subaochen/2017/08/def%E5%92%8Cedef%E9%87%8A%E7%96%91/

点赞(2)

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部