我尝试去理解 TeX 编程的精妙之处,但这并不总是那么容易。例如,在下面的代码中:
\def\first{abc}
\def\second{abc}
\ifx\first\second OK!\else false \fi
我明白为什么输出是OK!。
但我不明白为什么此代码的输出是错误的:
\if\first\second OK!\else false \fi
答案1
\if
比较以下两个标记后宏扩展,因为它想要比较不可扩展的标记。
因此\if\first\second...\fi
扩展\first
并且输入流现在
\if abc\second...\fi
a
并且和之间的比较b
返回false。
你可以通过以下方式使\first
和不扩展:\second
\if\noexpand\first\noexpand\second...\fi
\first
但这将返回 true,与和的含义无关\second
,因为\if
比较字符代码和,如果标记不是字符,则认为它具有字符代码 256(实际上并非如此,但这样想很方便)。除非已使用以下方式定义控制序列,否则将认为它具有字符代码 256
\let\cs=a
(或任何其他字符),在这种情况下\if a\cs
将返回 true。
当然,值 256 代表任何不能是字符代码的数字,因此它将适用0x11000
于 XeLaTeX 或 LuaLaTeX,其中字符代码可以高达0x10FFFF
。
的使用\if
并不容易,而且有很多微妙之处。例如,不能直接使用活动字符进行比较,并且必须在其前面(宏扩展之后)加上\noexpand
。
一个巧妙的例子\if
是测试一个参数是否为空:
\def\cs#1{%
\if\relax\detokenize{#1}\relax
The argument is empty%
\else
The argument #1 is non empty%
\fi
}
它使用\detokenize
e-TeX 特性。如果参数为空,则比较将在\relax
和之间进行\relax
,就其而言,它们是相等的\if
;否则,\detokenize
将返回一个字符串(类别代码为 12),并且\relax
永远不会等于 的字符\if
。因此,
\cs{abc}
一个人会得到
\if\relax abc\relax
The argument is empty%
\else
The argument #1 is non empty%
\fi
而真正的文本将是
bc\relax The argument is empty\else
将被丢弃。
同样地,
\if a\first true\else false\fi
扩展\first
给出
\if aabctrue\else false\fi
并且,由于后面两个不可扩展标记\if
相等,因此真实文本是
bctrue
而\else false\fi
将被丢弃。
答案2
根据 TeX By Topic,有三种\if
与标记有关的测试。要理解它们,您需要回想一下 TeX 中基本上有两种标记:
字符标记,由字符代码(类似于 ASCII 编码)和类别代码(这决定了 TeX 看到该字符时对该字符的解释)。
控制序列标记具有计算机名称这是一个字符串(但字符串中的字符不带任何一种代码),并且还具有以下某种行为:
如果控制序列由 TeX 预定义,则为基元。在这种情况下,两个不同的基元具有不同的行为。
内部,如果控制序列是由诸如
\countdef
或 之类的原语创建的\font
,并且使用 csname 来引用某个内部量(在前一种情况下,是计数寄存器;在后一种情况下,是字体)。此类控制序列的行为仅取决于它引用的量。宏,如果控制序列是由
\def
或其兄弟创建的。在这种情况下,其相关行为由辅助标记列表指定,扩张,以及与\outer
、\long
、 和相关的各种辅助标志\global
。
各种\if
测试根据上述分类中的相似性来比较两个标记。即:
\if<tok1><tok2>
比较仅有的字符代码,在此方案下,所有控制序列都具有相同的字符代码,但不等于任何实际字符的字符代码。小心!它会扩展其参数,直到找到两个不可扩展的标记进行比较。\ifcat<tok1><tok2>
比较仅有的类别代码,在此方案下,所有控制序列都具有相同的类别代码,且与任何实际字符的类别代码不相等。它还会扩展。\ifx<tok1><tok2>
使用更精细的比较,结合上述所有信息,通过两种代码区分字符,并根据行为区分控制序列和控制序列。它(出于显而易见的原因)确实不是扩展其论点。
这些测试可能看起来既空洞又令人困惑,这取决于你如何看待它们。例如,\if<tok1><tok2>
如果你想象写任何特定的实例,看起来真的很愚蠢:
\if00% True
\if01% False
\countdef\a0 \if\a0 or \if\a1% False, since \a is not a character
\countdef\a1\countdef\b1 \if\a\b% True, surprisingly (maybe)
\def\a{0}\def\b{1} \if\a\b% False, since they expand.
其中只有最后一种情况真正有用,并揭示了为什么 \if
需要扩展其参数才能起作用。基本上,\if#1#2
只要您确定在它后面写入的两个标记都扩展为一个标记(或者您知道如何处理替代方案),它就会有助于比较存储在宏(或宏参数,例如)中的两个字符。
类似地,但相反,\ifx
可能会相当令人困惑,因为它完全逆转测试的意义在于\if
它是如何进行的,在某些情况下:
\ifx00% True, still
\ifx01% False, still
\countdef\a0 \ifx\a0 \ifx\a1% False, still
\countdef\a0 \countdef\b1 \ifx\a\b% Now false!
\def\a{0} \def\b{1} \ifx\a\b% Now false!
不幸的是,也许\ifx
足够细粒度,可以识别数字或字符的两个“别名”之间的区别(通过 、 等完成\countdef
)\let
,但不是认识到相似别名和它所别名的事物之间,因为控制序列总是将 false 与其底层字符进行比较。
从好的方面来说,如果将两个字符串都存储在宏中,它确实提供了一个很好的内置字符串比较函数。对于一种处理精确测量但缺乏基本算术的语言来说,这是一种奇怪的复杂功能。
答案3
答案已经很详尽了;我只是想要一个片段,可以在输入后快速粘贴到终端 shell 中pdflatex
(仅此而已),作为快速提醒 - 这似乎对我有用:
\documentclass{article}
\begin{document}
%%%
%
\def\a{1}\def\b{1} \if\a\b{\typeout{T1}}\else{\typeout{F1}}\fi
% T1
%
\def\a{1}\def\b{0} \if\a\b{\typeout{T2}}\else{\typeout{F2}}\fi
% F2
%
\def\true{1}
%
\def\a{\true}\def\b{\true} \if\a\b{\typeout{T3}}\else{\typeout{F3}}\fi
% T3
%
\def\a{\true}\def\b{1} \if\a\b{\typeout{T4}}\else{\typeout{F4}}\fi
% T4
%
\def\a{\true}\def\b{0} \if\a\b{\typeout{T5}}\else{\typeout{F5}}\fi
% F5
%
%%%
%
\def\a{1}\def\b{1} \ifx\a\b{\typeout{T6}}\else{\typeout{F6}}\fi
% T6
%
\def\a{1}\def\b{0} \ifx\a\b{\typeout{T7}}\else{\typeout{F7}}\fi
% F7
%
\def\a{\true}\def\b{\true} \ifx\a\b{\typeout{T8}}\else{\typeout{F8}}\fi
% T8
%
\def\a{\true}\def\b{1} \ifx\a\b{\typeout{T9}}\else{\typeout{F9}}\fi
% F9
%
\def\a{\true}\def\b{0} \ifx\a\b{\typeout{T10}}\else{\typeout{F10}}\fi
% F10
%
%%%
%
\def\a{10}\def\b{10} \if\a\b{\typeout{T11}}\else{\typeout{F11}}\fi
% F11
%
\def\a{10}\def\b{10} \ifx\a\b{\typeout{T12}}\else{\typeout{F12}}\fi
% T12
%