\if 和 \ifx 之间的区别

\if 和 \ifx 之间的区别

我尝试去理解 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
}

它使用\detokenizee-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 中基本上有两种标记:

  1. 字符标记,由字符代码(类似于 ASCII 编码)和类别代码(这决定了 TeX 看到该字符时对该字符的解释)。

  2. 控制序列标记具有计算机名称这是一个字符串(但字符串中的字符不带任何一种代码),并且还具有以下某种行为:

    • 如果控制序列由 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
% 

相关内容