我定义了两个宏\newfoo{#1}{#2}
和\foo{#1}
。第一个参数用作键,必须是有效的 LaTeX 宏名称(不带反斜杠)。第二个参数是一小段文本。使用\newfoo
键与其文本相关联,并且使用\foo
文本应该被打印,给定先前定义的键。
对于熟悉 glossary/-ies 包的人来说,这些命令最好被描述为那些命令的穷人版本,但我在其他情况下需要它们。
在内部,\newfoo{#1}{#2}
定义一个宏\foo@#1
并\foo{#1}
扩展为\foo@#1
。下面的 MWE 可以工作。但是,如果\foo
为之前未定义的键调用,我本来预计会出现编译错误,因为\foo@#1
不存在。然而,编译默默进行,只是将不存在的扩展\foo@#1
为空。这种行为是不可取的,因为应该注意拼写错误的键。
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[utf8]{inputenc}
\makeatletter
\newcommand*\newfoo[2]{%
\expandafter\newcommand\csname foo@#1\endcsname{#2}%
}
\newcommand*\foo[1]{\csname foo@#1\endcsname}
\makeatother
\newfoo{a}{1st letter of alphabet}
\newfoo{b}{2nd letter of alphabet}
\begin{document}
\verb#\foo{a}# is expected to expand to ``\foo{a}''
\verb#\foo{b}# is expected to expand to ``\foo{b}''
\verb#\foo{c}# has not been defined by \verb#\newfoo{c}{...}# and should raise a compile error
\end{document}
答案1
解决方案是
\newcommand*\foo[1]{%
\ifcsname foo@#1\endcsname
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\csname foo@#1\endcsname}%
{\ERROR}%
}
或者,使用内核构造
\newcommand*{\foo}[1]{%
\@ifundefined{foo@#1}{\ERROR}{\csname foo@#1\endcsname}%
}
答案2
这是 的一个“特性”,\csname
以前未定义的命令被定义为\let
通过\relax
... 访问\csname
。\endcsname
通常这是不受欢迎的行为,但它在 LaTeX 的几个地方使用,因为它是在经典 TeX 中在仅扩展上下文中进行定义的唯一方法,因此可用于标记某些事物。(例如,它用于捕获在 中写入文件时遇到的有问题的字符filecontents
。)
答案3
完成一点点David Carlisle 的精彩回答,请注意,如果你\foo{c}
在你的例子中使用了它,那么不是已经扩展为“无”,因为\relax
实际上不是无。如果你尝试扩展\relax
,你会得到,\relax
因为它是不可扩展的。1但可以\relax
阻止宏扩展为无不会阻止的事情,例如:
- TeX 在读取 <整数常量>、<八进制常量>、<十六进制常量> 或 <glue> (以 <dimen><stretch><shrink> 的形式) 时的扩展过程;
- 当 TeX 正在寻找
\noalign
或\omit
位于对齐的开头(tabular
,align
...)或行的末尾时(在这种情况下,即使几个空格标记也不会停止搜索过程,但\relax
会,这可以防止\rowcolor
在某些情况下工作 - 参见这条信息例如); - 可能还有很多其他的事情。
因此,当 TeX 最终在其胃深处消化它时,该\relax
命令不会做太多的事情;但在其他层面上,它可以产生非常明显的效果。
在您的示例中,您可以通过在使用之前执行两个扩展步骤来亲自看到 的扩展\foo@c
是\relax
而不是空扩展\show
:第一步将 扩展\foo
(抓取{c}
)为\csname foo@c\endcsname
,第二步将 扩展\csname
(抓取foo@c\endcsname
)为\foo@c
:
\documentclass{article}
\makeatletter
\newcommand*\newfoo[2]{%
\expandafter\newcommand\csname foo@#1\endcsname{#2}%
}
\newcommand*\foo[1]{\csname foo@#1\endcsname}
\makeatother
\begin{document}
\expandafter\expandafter\expandafter\show\foo{c}
\end{document}
输出到终端:
> \foo@c=\relax.
<recently read> \foo@c
l.12 ...dafter\expandafter\expandafter\show\foo{c}
当然,下面的方法同样有效:
\foo{c}%
\makeatletter
\show\foo@c
\makeatother
但执行的副作用是\relax
(!),而且没那么有趣……
脚注
然而,请注意,如果
\relax
定义为\def\relax{\relax}
,它的行为将完全不同 -\relax
不是一个夸克。它仍然可以防止连字:
f\relax f\relax i
与比较ffi
。