这个问题可能一下子要回答很多,但我在一个旧帖子中看到了一些代码(粘贴在下面),但我真的不明白它是如何工作的。如果有人能仔细阅读代码并解释一下,我将不胜感激?(特别是 in 是如何#
工作\def\Assign#1
的,\ifcsname
以及 是\global
如何操作的)
\documentclass{article}
\makeatletter
\def\Assign#1{% if \DP@#1 is defined append to it otherwise create it
\ifcsname DP@#1\endcsname
\edef\DP@tmp{\csname DP@#1\endcsname,\@currentlabel}%
\else
\edef\DP@tmp{\@currentlabel}%
\fi
\global\expandafter\let\csname DP@#1\endcsname\DP@tmp
\typeout{ASSIGN: \csname DP@#1\endcsname}
}
\def\Assigned#1{%
% \expandafter\def\csname Assigned#1\endcsname{\csname DP@#1\endcsname}
\typeout{ASSIGNED: \csname DP@#1\endcsname}
}
\makeatother
\begin{document}
\begin{enumerate}
\item Apples \Assign{fred}
\item Oranges \Assign{julie}
\item Bananas \Assign{fred}
\item Mangos \Assign{julie}
\item Strawberries \Assign{julie}
\end{enumerate}
\Assigned{fred} % should print 1,3
\Assigned{julie} % should print 2,4,5
\end{document}
答案1
\def\foo#1{...#1...}
是定义单参数宏的原始 TeX 方式。
在 LaTeX 环境中,代码应该具有
\newcommand*{\Assign}[1]{...}
\newcommand*{\Assigned}[1]{...}
以检查宏是否已经定义,并且不会对奇怪的结果感到惊讶。
代码的目的是创建一个用逗号分隔的数字列表数组,每个项目都与一个名称相关联,并包含分配给此人的任务列表。
任务编号是通过 获得的\@currentlabel
,它扩展为最后一个用 步进的计数器的表示\refstepcounter
;在 内enumerate
,\@currentlabel
将扩展为当前项目编号(带有前缀,对于第一级enumerate
环境,该前缀为空)。
项目列表是逐步构建的,并存储在名为 的宏中\DP@<name>
;对于用户fred
,宏是\DP@fred
。但是,\DP@#1
由于 TeX 读取输入并将其拆分为标记的方式,使用 在代码中不起作用,因此\csname DP@#1\endcsname
必须使用 来形成正确的宏名称。
\ifcsname...\endcsname
如果由此产生的控制序列具有定义,则条件返回 true \csname...\endcsname
,否则返回 false。代码使用此方法测试是否\DP@<name>
存在:在第一种情况下,下一个项目编号将附加在逗号后,否则仅附加编号。
例如,第一个\Assign{fred}
将定义\DP@fred
为1
;第二个将附加,3
。
代码以间接的方式执行此操作,使用临时宏\DP@tmp
(这是一个糟糕的选择,因为如果其中一个用户名是tmp
混乱就会随之而来)。
在进行宏定义之前,对替换文本进行\edef\DP@tmp{...}
完全扩展,以完成预期的工作。
接下来,\DP@<name>
被赋予与临时宏相同的含义;这必须全局完成,因为本地宏定义会在环境结束时被遗忘enumerate
。
为控制序列分配与另一个标记相同含义的原始指令是\let
;如果以 为前缀\global
,则分配是全局的。但是在这种情况下,我们需要使用\csname
来形成受让人,但是
\global\let\csname DP@#1\endcsname\DP@tmp
不会起作用,因为它会\csname
与 相同D
。控制序列标记必须在\let
进入操作之前形成,因此\let
必须在 之前\expandafter
。根据 TeX 规则,\global
前缀会递归扩展下一个标记,直到找到可以合法添加前缀的东西(\let
,\def
以及一些其他原始命令)。因此
\global\expandafter\let\csname DP@#1\endcsname\DP@tmp
实际上,使用临时宏的中间步骤并不是真正需要的:
\makeatletter
\newcommand*{\Assign}[1]{% if \DP@#1 is defined append to it otherwise create it
\ifcsname DP@#1\endcsname
\expandafter\xdef\csname DP@#1\endcsname{\csname DP@#1\endcsname,\@currentlabel}%
\else
\expandafter\xdef\csname DP@#1\endcsname{\@currentlabel}%
\fi
\typeout{ASSIGN: \csname DP@#1\endcsname}
}
\newcommand*{\Assigned}[1]{%
% \expandafter\def\csname Assigned#1\endcsname{\csname DP@#1\endcsname}
\typeout{ASSIGNED: \csname DP@#1\endcsname}
}
\makeatother
也可以这样做,并避免与名为 的用户可能出现的问题tmp
。
定义中的注释\Assigned
行将生成一个“用户级”宏\Assigned<name>
。如果取消注释,则调用\Assigned{fred}
将在日志文件和控制台中打印任务列表,之后\Assignedfred
相当于\DP@fred
。
实现相同目标的更好方法是
\global\expandafter\let\csname Assigned#1\expandafter\endcsname\csname DP@#1\endcsname
我添加\global
它的原因与之前相同。