提问于:
浏览数:
3717
```
\documentclass{ctexart}
\begin{document}
\expandafter\xdef\csname kkk\endcsname{{0}{1}}%
\def\ppp#1#2{#1;#2;}
\expandafter\ppp\csname kkk\endcsname
\end{document}
```
得到错误信息
*Runaway argument?*
*! Paragraph ended before \ppp was complete.*
我想利用保存在 `\csname kkk\endcsname` 中的内容,该怎么做呢?
1 回答
16
你现在的写法,相当于执行了
```tex
\ppp\kkk
```
你需要让 `\kkk` 再展开一次。例如
```tex
\documentclass{article}
\begin{document}
\expandafter\xdef\csname kkk\endcsname{{0}{1}}%
\def\ppp#1#2{#1;#2;}
\expandafter\expandafter
\expandafter\ppp\csname kkk\endcsname
\end{document}
```
这篇[知乎文章](https://zhuanlan.zhihu.com/p/55749923)提到了一些介绍 `\expandafter` 的资料,可作为后续阅读。
------
补充
1. 以下两种定义方式无区别
```tex
\def\aaa{{1}{2}{3}}
\expandafter\def\csname aaa\endcaname{{1}{2}{3}}
```
2. `\csname ...\endcaname` 的展开一定是一个命令,是 `\<cmd>` 的形式。
3. `\csname ...\endcaname` 的意义是,让使用名字中包含特殊符号的命令更方便(不需要修改 category code)、可以动态拼出命令名。例如
```tex
% 使用一个命令,它的命令中包含 "@"
\csname abc@abc\endcsname
% 使用一个命令,它的命令名中包含空格
\csname abc abc\endcsname
% 定义一个命令,它的命令名(部分)由传入的参数决定
\def\specialCmd#1#2{%
\expandafter\def\csname special@#1\endcsname{#2}%
}
% 这样,下面两组命令就实现了相同的功能
% - group 1
\specialCmd{abc}{xxx}
% - group 2
\makeatletter
\def\special@abc{xxx}
\makeatother
```
4. 回到你的例子
1. 从最简单的开始,直接使用 `\ppp` 命令。
做法为:`\ppp{arg1}{arg2}`
1. 先把要传的参数定义成命令(`\def\kkk{{arg1}{arg2}}`),然后再传给 `\ppp`。此时,先写成 `\ppp\kkk`,有两个命令,需要先展开第二个、再展开第一个,所以在第一个命令前加上 `\expandafter`。
变成了:`\expandafter\ppp\kkk`
1. 让 `\kkk` 由 `\csname kkk\endcsname` 展开得到。先写成 `\expandafter\ppp\csname kkk\endcsname`,看前三个命令 `\expandafter\ppp\csname`,需要先对第三个展开一次,所以给第一个(`\expandafter`)、第二个命令(`\ppp`)前都加一个 `\expandafter`。
变成了:我最初回答内容中的样子。
1. 这全程,和 `\kkk` 里存了几个 token、几组大括号无关。有两组、四组、八组,都是三个 `\expandafter`
建议阅读本回答前半段提到的知乎文章中的参考资料,也可以去 tex.stackexchange.com 上搜索。多看一些内容、看靠谱的内容,附带看一些例子,还可以用 `unravel` 宏包提供的「在终端演示逐步展开」的功能帮助了解引擎是怎么展开的。
------
一个使用 `unravel` 宏包的例子
```tex
% main.tex
\documentclass{article}
\usepackage{unravel}
\begin{document}
\expandafter\xdef\csname kkk\endcsname{{0}{1}}%
\def\ppp#1#2{#1;#2;}
\unravel{%
\expandafter\expandafter
\expandafter\ppp\csname kkk\endcsname
}
\end{document}
```
保存为 `main.tex`,终端执行 `pdflatex main` 进行编译,会在遇到 `\unravel` 命令时停下来,每按一次回车展开一步。连续按回车,能得到完整的展开步骤
```
======== Welcome to the unravel package ========
"<|" denotes the output to TeX's stomach.
"||" denotes tokens waiting to be used.
"|>" denotes tokens that we will act on.
Press <enter> to continue; 'h' <enter> for help.
||
|> \expandafter \expandafter \expandafter \ppp \csname kkk\endcsname
[===== Step 1 =====] \expandafter \expandafter
|| \expandafter \expandafter
|> \expandafter \ppp \csname kkk\endcsname
[===== Step 2 =====] \expandafter \ppp
|| \expandafter \expandafter
|| \expandafter \ppp
|> \csname kkk\endcsname
[===== Step 3 =====] \csname = \csname
|| \expandafter \expandafter
|| \expandafter \ppp
|| \csname
|> kkk\endcsname
[===== Step 4 =====] \csname kkk\endcsname =\kkk
|| \expandafter \expandafter
|| \expandafter \ppp
|> \kkk
[===== Step 5 =====] back_input: \expandafter \ppp
|| \expandafter \expandafter
|> \ppp \kkk
[===== Step 6 =====] back_input: \expandafter \expandafter
||
|> \expandafter \ppp \kkk
[===== Step 7 =====] \expandafter \ppp
|| \expandafter \ppp
|> \kkk
[===== Step 8 =====] \kkk = macro:->{0}{1}
|| \expandafter \ppp
|> {0}{1}
[===== Step 9 =====] back_input: \expandafter \ppp
||
|> \ppp {0}{1}
[===== Step 10 =====] \ppp = macro:#1#2->#1;#2;
||
|> 0;1;
[===== Step 11 =====] 0= the character 0 : \everypar={}
||
|> 0;1;
[===== Step 12 =====] 0
<| 0
||
|> ;1;
[===== Step 13 =====] ;
<| 0;
||
|> 1;
[===== Step 14 =====] 1
<| 0;1
||
|> ;
[===== Step 15 =====] ;
<| 0;1;
||
|>
[===== End =====]
```
作者追问:2019-12-13 09:42
非常感谢!
我大体明白了 `\expandafter` 和 `\csname` 的用法。把 `\csname ... \endcsname` 多次展开的用法我是第一次遇到。我尝试了4次展开:
```
\documentclass{ctexart}
\begin{document}
\expandafter\xdef\csname savefourargs\endcsname{{0}{1}{2}{3}}%
\def\showfouritem#1#2#3#4{#1;#2;#3;#4;+}
\def\showtwoitem#1#2{#1;#2;+}
\expandafter\expandafter
\expandafter\showtwoitem\csname savefourargs\endcsname
\expandafter%1
\expandafter%2
\expandafter%1
\expandafter%3
\expandafter%1
\expandafter%2
\expandafter%1
\expandafter%4
\expandafter%1
\expandafter%2
\expandafter%1
\expandafter%3
\expandafter%1
\expandafter%2
\expandafter%1
\showfouritem\csname savefourargs\endcsname
\end{document}
```
不知道是不是可以这样理解:`\csname ... \endcsname` 中保存了几个花括号就要展开几次。如果可以这样理解的话,那 `\csname ... \endcsname` 的行为真的是有点奇特。
作者追问:2019-12-16 14:50
还是有点不太明白,感觉 `\csname ... \endcsname` 有点像套娃玩具,打开外面的娃,里面还有娃,它不会一下子把所有的娃都释放出来。就像前面的例子,非要让它展开4次。
这个例子:
```tex
\def\aaa{{1}{2}{3}}%
\def\bbb#1#2#3{#1;#2;#3;}%
\expandafter\bbb\aaa
```
只需要用 `\expandafter` 把`\aaa`展开一次,就可以。
那么用`\def\aaa{{1}{2}{3}}%`定义的 `\aaa`,
跟用`\expandafter\def\csname aaa\endcaname{{1}{2}{3}}%`定义的`\csname aaa\endcsname`本质上的区别是什么呢?
-
回复 undefined :这次明白了,第一次展开 \csname ... \endcsname 得到一个宏,第二次展开它才得到其中的内容。非常感谢,你列出的资料也很有用,谢谢! – 咸菜的味道 2019-12-16 19:25 回复
-
回复 undefined :更新了回答 – 论坛 github.com/CTeX 2019-12-16 16:55 回复
-
回复 undefined :「\csname ... \endcsname 中保存了几个花括号就要展开几次」不是 – 论坛 github.com/CTeX 2019-12-14 01:26 回复
-
回复 undefined :你理解偏了。\csname 一次展开后得到的是一个控制序列,后面事都是在操作这个控制序列,和 \csname 无关了。 – 论坛 github.com/CTeX 2019-12-14 01:26 回复
-
非常感谢! – 咸菜的味道 2019-12-13 09:44 回复
你的回答
请登录后回答
你的回答将会帮助更多人,请务必认真回答问题。