``` \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 回答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 回复

你的回答

请登录后回答

你的回答将会帮助更多人,请务必认真回答问题。