![嵌套 \noexpand 和 \edef 的问题](https://linux22.com/image/244040/%E5%B5%8C%E5%A5%97%20%5Cnoexpand%20%E5%92%8C%20%5Cedef%20%E7%9A%84%E9%97%AE%E9%A2%98.png)
我有一个 LaTeX 问题,想用以下代码片段来解释它:
\documentclass{article}
\begin{document}
\def\myline{A}
\edef\myline{\myline \noexpand\bf B}
\edef\myline{\myline C}
\myline
\end{document}
我必须逐步建立一行。这发生在 for 循环内。问题是我想用粗体突出显示一些文本,但每当我扩展我的行时,我都会收到以下错误:
! Missing control sequence inserted.
<inserted text>
\inaccessible
l.9 \myline
? X
我认为,这是因为第三个\edef
,它试图扩展\bf
第二个\edef
,但是我必须扩展它以避免无限递归。
答案1
让我们看看发生了什么
\def\myline{A}
\edef\myline{\myline \noexpand\bf B}
给定第一行,第二行相当于
\def\myline{A\bf B}
现在,当你说
\edef\myline{\myline C}
TeX 完全扩展了内部\myline
,它变成了这样(\show\myline
为避免错误的换行而进行编辑的结果):
> \myline=macro:
->A\protect \protect \protect \edef OT1{OT1}\let \enc@update \relax
\protect \edef cmr{cmr}\protect \edef m{m}\protect \edef n{n}
\protect \xdef \OT1/cmr/m/n/10 {\OT1/cmr/m/n/10 }\OT1/cmr/m/n/10
\size@update \enc@update \ignorespaces \relax \protect \relax
\protect \edef m{bx}\protect \xdef \OT1/cmr/m/n/10
{\OT1/cmr/m/n/10}\OT1/cmr/m/n/10 \size@update \enc@update BC.
这肯定不是 TeX 在执行新定义的时所希望看到的\myline
。
问题是,\noexpand
抑制扩张一次:第二个\edef
,令牌\bf
将要被扩展。这是“正确”的做法:
\documentclass{article}
\begin{document}
\makeatletter
\def\myline{A}
\protected@edef\myline{\myline \bfseries B}
\protected@edef\myline{\myline C}
\myline
\end{document}
注意,\protect
前面的\bfseries
是不需要的,因为\bfseries
已经是一个“LaTeX 强命令”。
另一种方法是使用etoolbox
:
\documentclass{article}
\usepackage{etoolbox}
\robustify\bfseries
\begin{document}
\def\myline{A}
\edef\myline{\myline \bfseries B}
\edef\myline{\myline C}
\myline
\end{document}
这将使\bfseries
内部“永远受到保护” \edef
。
马丁在他的回答中建议使用\appto
,这是一个通过累积构建宏的好方法:
\usepackage{etoolbox}
\def\myline{}
\appto\myline{A \bfseries B}
\appto\myline{C}
在这种情况下,我们得到的结果与
\def\myline{A \bfseries BC}
如果吸积必须是全局的,则可以使用\gappto
(相当于内核宏\g@addto@macro
)。
另一个策略是使用令牌寄存器:
\newtoks\mylinetoks
\mylinetoks={}
\mylinetoks={A \bfseries B}
\mylinetoks=\expandafter{\mylinetoks C}
当然,寄存器可以使用任意多次,只需分配一次即可。内核提供\addto@hook
:
\newtoks\mylinetoks
\makeatletter
\mylinetoks={}
\addto@hook\mylinetoks{A \bfseries B}
\addto@hook\mylinetoks{\mylinetoks C}
要使用寄存器的内容,你可以说
\the\mylinetoks
答案2
正如您自己所说,\bf
第二个 将被扩展。您还需要为此\edef
添加一个,其前缀必须为:\noexpand
\edef
\noexpand
\documentclass{article}
\begin{document}
\def\myline{A}
\edef\myline{\myline \noexpand\noexpand\noexpand\bf B}
\edef\myline{\myline C}
\myline
\end{document}
当然,如果您有更多,情况就会很快失控\edef
,特别是当您不知道确切的数字时。
更好的方法是使用\protect
和,\protected@edef
以确保将\protect
与扩展数无关。在正常文本中它不会执行任何操作,因此不会造成任何问题。
\documentclass{article}
\begin{document}
\def\myline{A}
\makeatletter
\protected@edef\myline{\myline \protect\bf B}
\protected@edef\myline{\myline C}
\makeatother
\myline
\end{document}
如果您想要简单地附加材料\myline
而不扩展任何内容,则可以使用\expandafter
:
\expandafter\def\expandafter\myline\expandafter{\myline <new stuff>}
因此,在再次定义之前,旧的定义会被扩展。
还有一个 LaTeX 宏,\g@addto@macro\yourmacro{<stuff>}
它可以将内容添加到给定的宏中,但是是全局的。它使用一个令牌寄存器 (托克斯) 为了这。
该etoolbox
包中有一些很好的宏,例如\appto\yourmacro{<stuff>}
仅在本地起作用的宏。
答案3
正如 egreg 指出的那样,\bf
和\textbf
都是LaTeX 保护。没有必要在\protect
他们后面\protected@edef
。
也许这就是约瑟夫赖特所指的:
\def\myline{A}
% \bfseries needs localization:
\@temptokena{{\bfseries B}}
\edef\myline{\myline \the\@temptokena}
问题是,当你做出定义时,它就\protect
变成了这样。你本可以这样做\relax
% In document preamble:
\makeatletter
\@ifdefinable\protedef{\let\protedef\protected@edef}
\makeatother
% Subsequently:
\def\myline{A}
\protedef\myline{\myline {\bfseries B}}
您无需加载包来增强功能\bf
,\bfseries
只需执行
\newcommand*\mybf{} % \mybf is definable.
\let\mybf\bfseries
\protected\def\bfseries{\mybf}
\def\myline{A}
\edef\myline{\myline {\bfseries B}}
但这会发生\bfseries
全局变化,其(与 \robustify 类似)只能在本地或文档主体内完成。
如果你想知道这如何不导致循环定义,以下是它在文档主体中的工作方式:
{\bfseries XX}
\bfseries->\mybf
\mybf->\protect\bfseries
{\relax}
\bfseries->\not@math@alphabet\bfseries\mathbf\fontseries\bfdefault\selectfont