
這是一個後續問題\csname 的 \global 變體...\endcsname,它要求提供那裡解釋的問題的解決方案,但不要求解釋。
問題的要點是
{ \gdef\foo{...} }
似乎表現不同
{ \expandafter\gdef\csname foo\endcsname{...} }
其中後者「在 save_stack 上新增一個保留條目」。
所以我想知道:
- 為什麼這兩個構造的行為會有差異?這只是實現方式的副作用
\csname...\endcsname
,還是有意的語意差異(據可能判斷)。 {retaining ...}
連結問題中提到的日誌檔案中的條目到底意味著什麼?為什麼在群組關閉後需要保留本地定義?
答案1
感謝您提出這個問題。我不太明白這個例子另一個問題當我讀到它時,但這個問題促使我再看一遍,我想我現在明白了。
基本上,從這個例子中還不清楚的是,當我們\expandafter\gdef\csname foo\endcsname{...}
在內部有幾個這樣的結構時,就會出現問題。相同的群組(或嵌套在該群組內的群組),即保存堆疊僅在群組內增長。 「retaining…」訊息是在退出群組時(彈出堆疊時)列印的,但該訊息本身並不是問題;它僅作為堆疊較早增長的證據。
該段落可能令人困惑,所以讓我們從頭開始了解保存堆疊。 :-)
1.考慮這個例子:
\def\a{hello}
{
\def\a{world}
}
在這裡,當內部群組中的 TeX 發現\a
被重新定義(到world
)時,它將先前的值(對包含 的標記清單的參考hello
)保存到保存堆疊中。然後,當它到達群組的末尾時,它會彈出堆疊以恢復 的定義hello
。這是保存堆疊的明顯原因,以下是相應的追蹤輸出,假設您有\tracingrestores=2
和\tracinggroups=2
以及 並且\tracingassigns=2
內部群組從第 10 行開始(比如說) - 我還修改了輸出以刪除換行符前{into…}
:
{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}
2.現在考慮同一個例子,沒有頂部\def\a{hello}
的:
{
\def\a{world}
}
— 再一次,當 TeX 看到\def\a{world}
組內部時,它必須保存 之前的含義\a
。它恰好是未定義的,但我們仍然需要在離開群組後再次將其重新定義為未定義,因此TeX需要將「未定義」的含義放入保存堆疊中。追蹤輸出為:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}
3.現在考慮一個類似的例子:
{
\let\a=\relax
\gdef\a{world}
}
在這裡,當 TeX 看到第一個 時\let\a=\relax
,它必須將先前的含義(「未定義」)保存到保存堆疊中,就像前面的範例一樣。然後,當它看到 時,\gdef
它不必將任何內容放入保存堆疊中。最後,當它到達群組的末尾並開始彈出其堆疊(當前包含 的“未定義”含義\a
)時,它注意到\a
現在具有全局定義,因此它忽略“未定義”含義並保留全局定義。這解釋了追蹤輸出:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}
4.最後,考慮這個例子:
{
\expandafter\gdef\csname a\endcsname{world}
}
事實證明,這與之前的情況完全相同。這是因為,當 TeX 看到 時,它\expandafter
會暫時跳過\gdef
並開始作用於下一個標記 ( \csname
) — 這意味著作用於\csname a\endcsname
創建宏\a
,並定義\relax
它是否尚未定義(這就是\csname
工作原理),並且只有在那之後它才會作用於(之前暫時忽略的)\gdef
並重新定義為( )\a
後面的新定義。world
因此追蹤輸出與上次相同:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}
這裡的所有都是它的。回答您的具體問題:
\csname … \endcsname
(1) 這只是實現方式的副作用;即它\let
是第一個的令牌\relax
。然而,這是有詳細記錄的,因此可以說也是語義的一部分(正如每個人所期望的那樣)。(2a)
{retaining ...}
日誌檔案中的條目表示(請參閱第 301 頁)教材)那早些時候\relax
放在保存堆疊上的定義,即巨集在設定為by之前的值\csname … \endcsname
,已被忽略\gdef
(並且全域定義已保留)。(2b)“為什麼在小組關閉後有必要保留本地定義?” — 事實並非如此,而且它們也沒有被保留。相反,你看到的是什麼時候該群組正在關閉,所有先前因同一組內所做的本地定義而保存的定義現在都將被檢查,並且如果在任何時候存在全域定義,則現在才被丟棄。在群組的末尾,保存堆疊將為空(或更確切地說,具有與進入群組時相同的大小)。
更具體地說,這是問題和解決方案的範例那個問題。提問者在一個群組內定義了很多宏(本質上),其方式大致相當於:
{
\expandafter\gdef\csname A\endcsname{I'm A}
\expandafter\gdef\csname B\endcsname{I'm B}
\expandafter\gdef\csname C\endcsname{I'm C}
}
等等。正如我們在上面的範例 3 和 4 中看到的,這相當於:
{
\let\A=\relax \gdef\A{I'm A}
\let\B=\relax \gdef\B{I'm B}
\let\C=\relax \gdef\C{I'm C}
}
等等。因此,上面的每個定義都會在保存堆疊上放置一個條目(用於控制序列名稱之前的含義\let
to\relax
by \csname ... \endcsname
),並且僅在群組的末尾才彈出所有這些條目。所以如果這樣的定義太多;您將用完“保存大小”。
這第一個發布的答案(作者:Steven B. Segletes)建議執行相當於\csname … \endcsname
在最頂層執行每個操作的操作(其中不會將任何內容放入保存堆疊中)。
這第二個發布的答案(Marcel Krüger)建議做相當於:
{
\begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
\begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
\begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}
其中定義發生在立即退出的群組內,因此每個堆疊都會立即彈出:追蹤輸出為(模換行符):
{entering simple group (level 1) at line 10}
{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}
{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}
{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}
{leaving simple group (level 1) entered at line 10}
如果您不關心可擴展等,而只想了解保存堆疊,那麼建議的解決方案如下:
{
{\let\A=\relax} \gdef\A{I'm A}
{\let\B=\relax} \gdef\B{I'm B}
{\let\C=\relax} \gdef\C{I'm C}
}
這樣您就可以明白為什麼保存堆疊不會繼續增長。