理解 \@ifnextchar

理解 \@ifnextchar

\makeatletter我理解和命令的定义\makeatother,也理解\def和的\newcommand一些含义。但是,简单地解释一下,下面这行代码到底发生了什么?

\@ifnextchar[ \@myitem{\@noitemargtrue\@myitem[\@itemlabel]}}

Gonzalo Medina 的回答如下:

在枚举环境中,强制在项目编号后换行

答案1

\@ifnextchar是一个 LaTeX 条件,它会提前查看以下字符。因此,它\@ifnextchar[会提前查看输入流中的以下字符是否为[(左括号)。如果为真,则它会执行紧随其后的标记,否则,它会跳过它并执行其后的标记。

当 的评估结果为真时,执行的第一个标记\@ifnextchar[\@myitem。如果评估结果为假,则执行{\@noitemargtrue\@myitem[\@itemlabel]}}- 以下组或“标记”,它\@myitem现在也使用可选参数集 进行调用[\@itemlabel]。在调用 之前\@myitem,布尔值\if@noitemarg设置为真(\@noitemargtrue)。

此表达式背后的一般思想是判断是否提供了可选参数,因此如果将参数传递给宏,而参数文本与给定的参数文本不匹配,则不会出现错误。使用上述示例,使用以下表达式\@mymacro定义

\def\@myitem[#1]{<replacement text>}

由于这\def用于定义,“可选”参数实际上不是可选的,但是必需的。这就是为什么上述用法是必要的。更传统的调节方式是使用

\newcommand{\@myitem}[1][<arg>]{<replacement text>}

<arg>如果没有明确指定,则指定可选参数的值。xparse提供类似的条件,对于上级用户来说,可能更直观。

答案2

这是 LaTeX 内核中非常重要的功能。该宏

\@ifnextchar

需要三个参数。第一个参数通常应为单个标记,[但不一定。

当输入流具有以下标记时

\@ifnextchar<token>{<true>}{<false>}

TeX 将查看下一个标记(跳过空格)并将其与<token>第一个参数进行比较,\@ifnextchar并将不是将其从输入流中删除。

如果两个标记一致(准确地说,具有相同的含义),TeX 将使用该<true>代码,否则使用该<false>代码。

定义带有可选参数和强制参数的命令的传统方法是这样的

\newcommand{\xyz}{\@ifnextchar[{\@xyz}{\@xyz[default]}}
\def\@xyz[#1]#2{do something with #1 and #2}

其中default是可选参数的默认值。现在我们会说

\newcommand\xyz[2][default]{something with #1 and #2}

(最终将以更安全的方式完成与旧式定义相同的工作)。如果调用

\xyz[a]{b}

的测试\@ifnextchar结果为真,因此 TeX 会将其扩展为

\@xyz[a]{b}

因为[不是删除,因为这将是参数。使用

\xyz{b}

测试结果为假,因此 token 将被替换为

\@xyz[default]{b}

使 TeX 满足 的定义\@xyz


这同样适用于您的情况,不同之处在于文本<false>中包含更多内容:

  1. \@ifnextchar[\@myitem{\@noitemargtrue\@myitem[\@itemlabel]}}[aaa] bbb会变成

    \@myitem[aaa] bbb
    
  2. \@ifnextchar[\@myitem{\@noitemargtrue\@myitem[\@itemlabel]}} bbb会变成

    \@noitemargtrue\@myitem[\@itemlabel] bbb
    

因此在方括号之间提供合适的参数\@myitem并设置条件。

由于本例中的第二个参数\@ifnextchar是单个标记,因此不需要用括号括起来。代码

\@ifnextchar[{\@myitem}{\@noitemargtrue\@myitem[\@itemlabel]}}

是完全等价的。


这个答案解释\@ifnextchar一下\futurelet另一个以获得描述\futurelet(均由 Martin Scharrer 撰写)。

答案3

你想知道这句话的具体含义

\@ifnextchar[ \@myitem{\@noitemargtrue\@myitem[\@itemlabel]}}

做?

让我们介绍更好的换行方式:

\@ifnextchar[% <-This is the 1st argument.
            \@myitem% <-This is the 2nd argument.
            {\@noitemargtrue\@myitem[\@itemlabel]}% <-This is the 3rd argument.
            }%<- This is the next non-space token following the three arguments.

\@ifnextchar接受三个非可选参数,然后“查看”这三个参数后面的下一个非空格标记,并将其(但如果存在则不包括空格标记)留在原处。

如果该非空间标记的含义与第一个参数中传递的标记的含义相同,\@ifnextchar则将传递第二个参数。否则,它将传递第三个参数。

通过您的代码片段,\@ifnextchar将会发现左括号的含义与右括号的含义不同,因此将提供第三个参数,\@noitemargtrue\@myitem[\@itemlabel]同时保留右括号。

最后你会得到:

\@noitemargtrue\@myitem[\@itemlabel]}

我不知道那个右括号从何而来。

似乎您在复制 Gonzalo Medina 的答案中的片段时忽略了将其删除的需要,其中出现以下宏定义:

\def\myitem{%
  \@ifnextchar[ \@myitem{\@noitemargtrue\@myitem[\@itemlabel]}}

根据该定义扩大\myitem收益率:

\@ifnextchar[ \@myitem{\@noitemargtrue\@myitem[\@itemlabel]}

这意味着后面的下一个非空格标记的含义\myitem将与类别代码 12 个字符标记的左括号的含义进行比较。
如果含义相同,\@myitem则将放置在该非空格标记的前面,结果为\@myitem[...
如果含义不同,\@noitemargtrue\@myitem[\@itemlabel]则将放置在该非空格标记的前面,结果为\@noitemargtrue\@myitem[\@itemlabel]<whatsoever token whose meaning does not equal opening-bracket>...


无论如何——\@ifnextchar工作原理如下:

\@ifnextchar<undelimited argument 1 containing a <single token>>%
            <undelimited argument 2>%
            <undelimited argument 3>%
            <optional space tokens>
            <non space token>

如果<single token>来自的含义<undelimited argument 1>= 的含义<non space token>,则交付<undelimited argument 2>并留<non space token>在原地,产生:
<undelimited argument 2><non space token>。如果来自
的含义=/= 的含义,则交付并留在原地,产生:。<single token><undelimited argument 1><non space token><undelimited argument 3><non space token>
<undelimited argument 3><non space token>


请注意,\@ifnextchar执行许多分配。因此它不能在纯扩展上下文(例如,内部\csname..\endcsname)中使用,因为扩展发生在 TeX 的嘴里,而分配发生在 TeX 的胃里。

请注意,即使在纯扩展上下文之外,也\@ifnextchar无法可靠地找出以下内容是否与from<non space token>是相同的标记。您只能找出这些标记是否具有相同的<single token><undelimited argument 1>意义

请注意\@ifnextchar不能用于确定后面是否有空格标记。一方面,第三个参数后面的空格标记是可选的,因此将被默默丢弃,而不是成为比较对象。另一方面,由于的定义存在缺陷,<single token>作为 <undelimited argument 1 containing a <single token>>空格标记的 会产生不可预测的行为\@ifnextchar

例如,尝试:

\documentclass{article}
\makeatletter
\begin{document}

\def\reserved@a#1\@ifnch{Let's bail out!}

\@ifnextchar{ }{some }{no }space?

\end{document}

还要注意,这<undelimited argument 1 containing a <single token>>是一个无界参数。使用无界参数时,catcode-1-character-tokens 和 catcode-2-character-tokens(即打开括号字符标记和关闭括号字符标记)必须匹配。因此,您不能传递单个括号标记。
如果人们希望检查括号标记是否跟在后面,他们通常会使用变通方法 \let\bgroup={\let\egroup=}\@ifnextchar\bgroup{an opening brace follows}{something differing from opening brace follows}各自\@ifnextchar\egroup{a closing brace follows}{something differing from closing brace follows}
但这种变通方法并不可靠,因为:
\@ifnextchar\bgroup{an opening brace follows}{something differing from opening brace follows}{an opening brace follows{

\@ifnextchar\bgroup{an opening brace follows}{something differing from opening brace follows}\bgroupan opening brace follows\bgroup
各自
\@ifnextchar\egroup{a closing brace follows}{something differing from a closing brace follows}}a closing brace follows}

\@ifnextchar\egroup{a closing brace follows}{something differing from a closing brace follows}\egroupa closing brace follows\egroup


人们通常使用\@ifnextchar不从输入文件中读取而是作为宏参数传递的标记进行测试。

请注意,有一种简单的方法可以可靠地测试未分隔宏参数的第一个标记是否为 catcode-1-character-token(左括号) - 该测试也可以应用于扩展上下文:

\long\def\UDfirstoftwo#1#2{#1}%
\long\def\UDsecondoftwo#1#2{#2}%
%%----------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%......................................................................
%% \UDCheckWhetherBrace{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has leading
%%                       catcode-1-token>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has no leading
%%                       catcode-1-token>}%
\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0\expandafter\UDsecondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UDfirstoftwo\expandafter{\expandafter
  \UDsecondoftwo\string}\expandafter\expandafter\UDfirstoftwo{ }{}%
  \UDfirstoftwo}{\expandafter\expandafter\UDfirstoftwo{ }{}\UD@secondoftwo}%
}%

这个测试的要点是将一个点附加到确保非空性的参数上,然后“命中”并分叉,\string无论\string命中的是左括号还是其他东西。
为了让事情更明显,我将更改换行并添加一些注释:

\long\def\UDfirstoftwo#1#2{#1}%
\long\def\UDsecondoftwo#1#2{#2}%
%%----------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%......................................................................
%% \UDCheckWhetherBrace{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has leading
%%                       catcode-1-token>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked has no leading
%%                       catcode-1-token>}%
\long\def\UDCheckWhetherBrace#1{%
  \romannumeral0% \romannumeral-expansion is used for ensuring 
                %  that there will be no expansion-step in
                % between whereafter unbalanced \if..\else..\fi remain.
  \expandafter\UDsecondoftwo %<-This is the "important \UDSecondoftwo"
  \expandafter{%
  \expandafter{%
  \string#1.% \string might hit an opening brace or something else. 
            % In any case \string cannot hit a closing brace as for one
            % thing #1 in any case is brace balanced and for another thing
            % the trailing dot catches up the case of #1 being empty.      
  }%<-This is the end of important \UDSecondoftwo's 
   %  first argument in case #1's first token is an opening brace.
  \expandafter\UDfirstoftwo %<-This is the "interesting \UDfirstoftwo".
  \expandafter{%
    \expandafter\UDsecondoftwo
    \string}%
    % The following will terminate \romannumeral's search for digits
    % after having `\romannumeral` carrying out \UDfirstoftwo:
    \expandafter\expandafter\UDfirstoftwo{ }{}\UDfirstoftwo
  }%<-This is the end of important \UDSecondoftwo's
   %  first argument in case #1 is empty or #1's first token is 
   %  not an opening brace.
   %     This also is the end of interesting \UDfirstoftwo's first
   %     argument in case #1's first token is an opening brace.
  {%
    % The following will terminate \romannumeral's search for digits
    % after having `\romannumeral` carrying out \UDsecondoftwo:
    \expandafter\expandafter\UDfirstoftwo{ }{}\UD@secondoftwo
  }%
}%

通过此测试

\UDCheckWhetherBrace{text}{leading brace}{no leading brace}产量:no leading brace \UDCheckWhetherBrace{{text}}{leading brace}{no leading brace}产量:leading brace \UDCheckWhetherBrace{{}text}{leading brace}}{no leading brace}产量:leading brace

相关内容