我在这里问过这个问题不止一次,但我得到的回答都是“使用非空宏更安全”。我从未被告知原因,而且(更重要的是)从未明确说明宏应该包含什么。
此代码示例是一个“循环”宏,它迭代其参数的每个标记:
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx#1\zEnd
\else
#1%
\expandafter\zzIterator
\fi
}
\def\zIterator#1{\zzIterator#1\zEnd}
\zIterator{hello}
有两个问题:
- 为什么
\def\zEnd{\zEnd}
不应该为空(不应该是\def\zEnd{}
)? - 花括号内的宏具体是什么真的很重要吗
\zEnd
,不管所括起来的宏是它本身\zEnd
还是其他什么(例如\zIterator
,\zzIterator
或诸如此类)?
答案1
使用“夸克”(定义为扩展为自身的宏)的原因是它永远无法匹配还要别的吗.如果您使用
\def\zEnd{}
那么你的标记\ifx
等于\empty
,所以像
\zIterator{hello\empty more tokens}
将提前停止。除了标记本身之外,其他任何定义都一样:您需要确保标记不在输入中。使用夸克,我们只有一个“受限”标记。
答案2
此代码示例是一个“循环”宏,它迭代其参数的每个标记:
\def\zEnd{\zEnd} \def\zzIterator#1{% \ifx#1\zEnd \else #1% \expandafter\zzIterator \fi } \def\zIterator#1{\zzIterator#1\zEnd} \zIterator{hello}
有两个问题:
- 为什么
\def\zEnd{\zEnd}
不应该为空(不应该是\def\zEnd{}
)?- 花括号内的宏具体是什么真的很重要吗
\zEnd
,不管所括起来的宏是它本身\zEnd
还是其他什么(例如\zIterator
,\zzIterator
或诸如此类)?
该令牌\zEnd
用作结束迭代/递归的“标记令牌”。
广告 1:\ifx
-comparison 通过比较标记的含义来触发循环的终止。实际上,除了 之外,不会有很多标记\zEnd
被定义为扩展为。但可能有许多短宏在某个阶段(暂时)被定义为根本不传递任何标记。如果被定义为\zEnd
,则此类宏标记会错误地终止循环。\zend
\def\zEnd{}
广告 2:这无所谓。重要的是,选择/定义标记标记的方式应确保在参数/要迭代的标记集中不会出现具有相同含义的标记。
顺便说一句:“迭代其参数的每个标记”的说法是错误的:
底层循环宏\zzIterator
不处理单个标记,但会处理非分隔参数。因此,循环宏不会遍历其参数的每个标记,而是按非分隔参数的方式遍历其参数,直到遇到具有两个含义相同的前导标记的非分隔参数,或遇到含义与 含义相同的标记\zEnd
。
“非分隔参数”意味着删除一级花括号(如果存在)并丢弃明确的空格标记。
例如,看看会发生什么:
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx#1\zEnd
Iteration ends.
\else
Iteration processes: #1 %
\expandafter\zzIterator
\fi
}
\def\zIterator#1{\zzIterator#1\zEnd}
\def\gobble#1{}%
\let\foobar=\zEnd
\zIterator{hel\foobar lo\gobble}
\bigskip
\zIterator{h{ell}o}
\bigskip
\zIterator{ {h} {ell} {o} }
\bigskip
\zIterator{he{ll\gobble}o\gobble}
\bigskip
% Don't do:
%\zIterator{he{ll}o}
% Here \ifx will compare l to l and then \zEnd will be carried out,
% yielding a never terminating recursion as \zEnd calls itself again
% and again...
\bye
反转要比较的标记\ifx
会更安全一些,因为您不会陷入\zIterator{he{ll}o}
陷阱:
\def\zEnd{\zEnd}
\def\zzIterator#1{%
\ifx\zEnd#1%<---- reversed.
\else
#1%
\expandafter\zzIterator
\fi
}
\def\zIterator#1{\zzIterator#1\zEnd}
\zIterator{hello}
该循环仍然可以通过参数中等于\zEnd
和(不平衡)\if..
/ \else
/ 的标记来超越。\fi
检测参数是否为空更加安全一些:
\long\def\firstoftwo#1#2{#1}
\long\def\secondoftwo#1#2{#2}
%%=============================================================================
%% Check whether argument is empty:
%%=============================================================================
%% \CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% Due to \romannumeral0-expansion the result is delivered after two
%% expansion-steps/after two "hits" by \expandafter.
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
\long\def\CheckWhetherNull#1{%
\romannumeral0\expandafter\secondoftwo\string{\expandafter
\secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\secondoftwo\string}\expandafter\firstoftwo\expandafter{\expandafter
\secondoftwo\string}\firstoftwo\expandafter{} \secondoftwo}%
{\firstoftwo\expandafter{} \firstoftwo}%
}%
%%=============================================================================
\long\def\IIterator#1#2{%
\expandafter\CheckWhetherNull\expandafter{\firstoftwo{}#1}{%
Iteration terminated.%
}{%
Processing: #2 %
\expandafter\IIterator\expandafter{\firstoftwo{}#1}%
}%
}%
\long\def\Iterator#1{\IIterator{#1{}}#1{}}%
%%
\Iterator{hello}%
\bigskip\par
\Iterator{he{ll}o}%
\bigskip\par
Of course this does---like the other variants---not take space-tokens
into account because \firstoftwo{\TeX}{} discards space-tokens that
precede non-delimited arguments:
\bigskip\par
\Iterator{ h e l l o }%
\bye