如何改变参数的值(例如#1)使得对它的任何引用(参数#1)都会扩展为新值?

如何改变参数的值(例如#1)使得对它的任何引用(参数#1)都会扩展为新值?

我想知道如何#1根据它的值将它更改为其他值,以便#1在更改之后的任何值都包含修改后的值,而不是原始值(从外部传递给宏)。

\def\myT#1%
  {%
    \ifnum#1=0
      % #1=1
    \fi
    #1
  }
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

编辑(解决方法;宁愿#1直接改变 的值,比如#1=1,而不是扩展 的嵌套参数\myT1如下所示):

\def\myT#1%
  {%
    \ifnum#1=0
      \myT{1}%
    \else
      #1%
    \fi
  }
\myT{0} % 1
\myT{1} % 1
\myT{2} % 2

答案1

#1除了使用适当的参数之外,您不能“分配”值。

您提出的任务类型可以更方便地使用两个宏来完成;虽然您可以\myT{1}在原始参数为时调用0,但这是低效的,因为它再次执行已经完成的检查。

\def\myT#1{%
  \ifnum#1=0
    \myTaux{1}%
  \else
    \myTaux{#1}%
  \fi
}
\def\myTaux#1{-#1-\par}

\myT{0} % 1
\myT{1} % 1
\myT{2} % 2

\bye

将所需功能的工作分成两个或多个宏并没有什么坏处;相反,这通常是一种很好的做法。

答案2

请记住,tex 是一种宏处理语言,而不是函数式语言。#1不是指向通过引用传递给函数的变量的标识符,它只是一个内联提供的标记的占位符。

因此,分配任何内容是否有意义取决于#1具体#1是什么。

给定

\def\myT#1%
  {%
    \ifnum#1=0
       #1=1 %HERE
    \fi
    #1 %THERE
  }
\myT{0} % 1

此处标记的行 0=1不是赋值,它只是排版 0=1 但是如果你使用相同的定义但将其称为

\newcount\zzz
\zzz=0
\myT{\zzz}

然后,标记为 HERE 的行将是\zzz=1并且将是一个分配,但是标记为 THERE 的行将是\zzz因此不会排版1它需要\the#1将被评估为\the\zzz

答案3

最接近您描述的“改变参数的值”的过程是让相关宏再次调用自身并改变相关参数。

这种编程技术称为尾递归,根据某些条件,例程所做的最后一件事是终止并再次调用自身。

用 (La)TeX 术语来说:当 (La)TeX 宏根据某些条件扩展它之后产生的最后一个标记形成对它的另一次调用时,该宏是尾递归的。

当您需要\if.. \else.. \fi-switches 来检查尾部递归的终止条件时,请确保在再次调用相关宏之前,处理/丢弃输入缓冲区中形成匹配\else- 和\fi-分支的标记。否则,相关宏不是尾部递归的,因为形成这些分支的标记会保留并累积在标记流/输入缓冲区中,直到在递归已经终止的时间点进行处理,从而对语义嵌套造成不必要的损害,并导致输入缓冲区中不必要的标记累积。

例如,您可以做这样的事情:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
   {\myT{1}}{#1}%
}%
\myT{0}% 1

\myT{1}% 1

\myT{2}% 2
%...
\bye

如果您需要进行更复杂的更改,在触发所需的扩展步骤后交换参数可能会很好。假设一个处理两个参数的宏,如果第一个参数的值为 0,则需要将该参数替换为值为 1 的参数,而第二个参数需要替换为值增加 1 的参数:

% this example requires eTeX-extensions (\numexpr):
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
\def\myT#1#2{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
  {%
    \expandafter\passfirsttosecond\expandafter{\number\numexpr#2+1\relax}{\myT{1}}%
  }{%
    Value of 1st arg: #1.\hfil\break
    Value of 2nd arg: #2.%
  }%
}%
\myT{0}{5}% Value of 1st arg: 1.\hfil\break Value of 2nd arg: 6.

\myT{1}{6}% Value of 1st arg: 1.\hfil\break Value of 2nd arg: 6.

\myT{2}{7}% Value of 1st arg: 2.\hfil\break Value of 2nd arg: 7.
%...
\bye

如果需要更改两个以上的参数,则可以嵌套调用\passfirsttosecondwithin\passfirsttosecond的第二个参数。如果这样做,您将得到一个模式,其中需要更改的最后一个(倒数第 k 个)宏参数的更改将在执行第一个(倒数第 k 个)指令之前执行\passfirsttosecond

假设一个宏可以处理九个参数,如果第一个参数的值为 0,则需要将该参数替换为值为 1 的参数,并且需要将以下每个参数替换为值增加 1 的参数:

% this example requires eTeX-extensions (\numexpr):
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\passfirsttosecond#1#2{#2{#1}}%
\def\myT#1#2#3#4#5#6#7#8#9{%
  \ifnum#1=0 \expandafter\firstoftwo\else\expandafter\secondoftwo\fi
  {%
    \expandafter\passfirsttosecond\expandafter{\number\numexpr#9+1\relax}{%
      \expandafter\passfirsttosecond\expandafter{\number\numexpr#8+1\relax}{%
        \expandafter\passfirsttosecond\expandafter{\number\numexpr#7+1\relax}{%
          \expandafter\passfirsttosecond\expandafter{\number\numexpr#6+1\relax}{%
            \expandafter\passfirsttosecond\expandafter{\number\numexpr#5+1\relax}{%
              \expandafter\passfirsttosecond\expandafter{\number\numexpr#4+1\relax}{%
                \expandafter\passfirsttosecond\expandafter{\number\numexpr#3+1\relax}{%
                  \expandafter\passfirsttosecond\expandafter{\number\numexpr#2+1\relax}{%
                    \expandafter\passfirsttosecond\expandafter{\number\numexpr#1+1\relax}{%
                      \myT
                    }%
                  }%
                }%
              }%
            }%
          }%
        }%
      }%
    }%
  }{%
    Value of 1st arg: #1.\hfil\break
    Value of 2nd arg: #2.\hfil\break
    Value of 3rd arg: #3.\hfil\break
    Value of 4th arg: #4.\hfil\break
    Value of 5th arg: #5.\hfil\break
    Value of 6th arg: #6.\hfil\break
    Value of 7th arg: #7.\hfil\break
    Value of 8th arg: #8.\hfil\break
    Value of 9th arg: #9.%
 }%
}%
\myT{0}{4}{9}{14}{19}{24}{29}{34}{39}%
%    Value of 1st arg: 1.\hfil\break
%    Value of 2nd arg: 5.\hfil\break
%    Value of 3rd arg: 10.\hfil\break
%    Value of 4th arg: 15.\hfil\break
%    Value of 5th arg: 20.\hfil\break
%    Value of 6th arg: 25.\hfil\break
%    Value of 7th arg: 30.\hfil\break
%    Value of 8th arg: 35.\hfil\break
%    Value of 9th arg: 40.%

\myT{1}{5}{10}{15}{20}{25}{30}{35}{40}%
%    Value of 1st arg: 1.\hfil\break
%    Value of 2nd arg: 5.\hfil\break
%    Value of 3rd arg: 10.\hfil\break
%    Value of 4th arg: 15.\hfil\break
%    Value of 5th arg: 20.\hfil\break
%    Value of 6th arg: 25.\hfil\break
%    Value of 7th arg: 30.\hfil\break
%    Value of 8th arg: 35.\hfil\break
%    Value of 9th arg: 40.%

\myT{2}{1}{2}{3}{4}{5}{6}{7}{8}%
%    Value of 1st arg: 2.\hfil\break
%    Value of 2nd arg: 1.\hfil\break
%    Value of 3rd arg: 2.\hfil\break
%    Value of 4th arg: 3.\hfil\break
%    Value of 5th arg: 4.\hfil\break
%    Value of 6th arg: 5.\hfil\break
%    Value of 7th arg: 6.\hfil\break
%    Value of 8th arg: 7.\hfil\break
%    Value of 9th arg: 8.%
%...
\bye

从上面的例子可以看出,(La)TeX 编程,尤其是 (La)TeX 的“扩展”概念,并不是为变量分配值,而是更多地关于旋转所谓的标记,同时您可以将标记用于一个接一个地放置在标记流/输入流中的东西/项目。

当我们接触 (La)TeX 中的宏扩展概念时,以下类比可能会有所帮助:

当 (La)TeX 处理 .tex 输入文件(即某段 (La)TeX 源代码)时,它在第一阶段会从该 .tex 输入文件中获取一组指令,用于将标记插入到标记流中。(这些标记可能是任何类别代码的字符标记、控制字标记和控制符号标记。)在后续阶段,将处理这些标记。在扩展阶段,标记将被移除/替换为其他标记。在扩展阶段,标记在标记流中的出现顺序也可以更改。

如果你对 (La)TeX 在读取 .tex 输入文件时将标记插入到标记流中所遵循的规则的详细信息感兴趣,请参阅讨论“源代码缩进”你可能会感兴趣

如果你对 (La)TeX 中有哪些类型的标记感兴趣,可以参阅讨论“‘宏’和‘命令’有什么区别?”你可能会感兴趣

如果你对 (La)TeX 中如何处理宏参数感兴趣,可以参阅讨论“TeX 如何查找分隔参数?”你可能会感兴趣

如果你对扩展欺骗感兴趣,讨论“如何才能知道附加到 csname 宏时的 expandafter 数量?”你可能会感兴趣


正如 siracusa 已经指出的那样,当 (La)TeX 从标记流中收集该参数时,您不能将另一个标记序列分配给宏参数。
但您可以让 (La)TeX 检查该参数以“决定”最终要传递哪些标记。这可以直接完成,也可以通过调用另一个宏来完成,其中传递的参数取决于检查的结果。

直接方法示例:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
  \ifnum#1=0 %<- The space before the percent terminates \ifnum's second number, i.e., the number 0. It gets discarded silently.
    \expandafter\firstoftwo%<-\expandafter removes the tokens that form the else-branch and the \fi
  \else
    \expandafter\secondoftwo%<-\expandafter removes the \fi
  \fi
  {1}{#1}%
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

通常\romannumeral用于获取以小写字母罗马表示的整数标记。当\romannumeral搜索数字时,(La)TeX 会不断扩展可扩展标记,直到找到数字或引发错误消息。如果所讨论的数字不是正数,(La)TeX 会默默地吞下它而不提供任何标记。因此,只要您确保 (La)TeX\romannumeral最终找到一个非正数,您就可以 (ab?) 使用它来触发大量扩展工作和参数检查工作。

直接方法的另一个例子,调用\romannumeral作为扩展的触发器,以确保在任何情况下的结果都在两个扩展步骤之后/在“命中”两次\myT找到的地方之后传递:\myT\expandafter

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\myT#1{%
   \romannunmeral0%<-While searching the number for \romannumneral, (La)TeX finds the digit-token 0 and keeps
                  %   triggering expansion while searching in the token-stream for
                  %   more digit-tokens or something that terminates the number.
   \ifnum#1=0 %<- The space before the percent terminates \ifnum's second number, i.e., the number 0. It gets discarded silently.
     \expandafter\firstoftwo%<-\expandafter removes the tokens that form the else-branch and the \fi
   \else
     \expandafter\secondoftwo%<-\expandafter removes the \fi
   \fi
   { 1}{ #1}% <- The space that precedes `1`/`#1` will be right
            %    behind the "0" from "\romannumeral0".
            %    Therefore it stops (La)TeX's seeking for digits for
            %    \rommanumeral which means that after doing
            %    expansion-work/argument-examining-work (La)TeX
            %    does only find the non-positive number "0" and 
            %    thus the result/effect of `\romannumeral` is:
            %    "Triggering doing a lot of expansion- and argument-
            %     examining-work while silently discarding the
            %     digit-token 0 which forms a non-positive number
            %     and the space behind it and not delivering
            %     anything in roman-notation at all."
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

调用另一个宏的示例:

\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\newcommand\myinnerT[1]{%
  Here \firstoftwo{\LaTeX}{} can do to the argument, #1, whatever shall be done.%
}%
\def\myT#1{%
  % Here the argument is examined for deciding how to call \myinnerT:
  \ifnum#1=0 %
    \expandafter\firstoftwo
  \else
    \expandafter\secondoftwo
  \fi
  {\myinnerT{1}}{\myinnerT{#1}}%
}%
\myT{0} % 1
\myT{1} % 1
\myT[2} % 2

请注意,上述示例并未实现对参数在扩展的某个阶段是否产生数字的检查。(严格地说,如果参数可以由任意标记序列组成,则无法进行这种检查,因为在这种情况下,构成参数的标记本身可能构成任何算法的 (La)TeX 实现,因此这种检查(在其他事情之下)需要解决停机问题

相关内容