定义 \newcounter{counter}[resetwith] 时正确设置 \thecounter

定义 \newcounter{counter}[resetwith] 时正确设置 \thecounter

使用\newcounter{counter}[resetwith],我可以定义一个计数器,当发生变化时自动重置resetwith

然而,似乎 的\thecounter定义只是显示 的值counter,而没有表明resetwith它与 的值有关。

这让我感到非常困惑,因为我习惯于\newtheorem{counter}{Display name}[resetwith]“正确地”定义事物以获得一致的定理编号。

当然我可以\thecounter在具体的案例中手动定义。

但是,我正在编写一个文件,该文件将在几种情况下被包含和使用:哪个计数器是resetwith 或者是否会自动重置取决于上下文,所以我正在寻找一个\newcounter能够以通用方式满足我需求的“升级”版本。

有没有“好”的方法来做到这一点?

目前我正在\edef\@temp{\noexpand\newcounter{question}\resetlevel}\@temp使用\resetlevel一个宏,该宏可以扩展为无内容或方括号之间的计数器名称。

为了让事情变得更有趣,我还使用了 hyperref,它似乎可以用计数器定义做一些事情。

答案1

让我们看一下 ; 的定义,以便我将使用和 来\setcounter举例。\newcounter{foo}\newcounter{foo}[baz]

% latex.ltx, line 2157:
\def\newcounter#1{%
  \expandafter\@ifdefinable \csname c@#1\endcsname
    {\@definecounter{#1}}%
  \@ifnextchar[{\@newctr{#1}}{}}

为了查看计数器是否尚未定义,LaTeX 会检查 ,\c@foo因为正如我们将看到的,相关计数寄存器的名称是通过在计数器名称前加上 来获得的c@。如果成功,\@definecounter{foo}则执行:

\def\@definecounter#1{\expandafter\newcount\csname c@#1\endcsname
     \setcounter{#1}\z@
     \global\expandafter\let\csname cl@#1\endcsname\@empty
     \@addtoreset{#1}{@ckpt}%
     \global\expandafter\let\csname p@#1\endcsname\@empty
     \expandafter
     \gdef\csname the#1\expandafter\endcsname\expandafter
          {\expandafter\@arabic\csname c@#1\endcsname}}

计数寄存器被分配,\newcount\c@foo并且为了安全起见,\setcounter{foo}{0}被发出。

\cl@foo然后定义一个全局宏并将其初始化为空;它将保存在foo步进时应重置的计数器的名称。然后将计数器名称添加到操作\cl@ckpt期间使用的虚拟计数器的重置列表中。\include

定义全局宏\p@foo(打印计数器值时的“前缀”)并初始化为空。

最后,\thefoo基本上是全局定义为默认表示\arabic{foo}

如果还跟着可选参数,比如的情况\newcounter{foo}[baz],也会\@newctr{foo}[baz]执行。

% latex.ltx, line 2162:
\def\@newctr#1[#2]{%
  \@ifundefined{c@#2}{\@nocounterr{#2}}{\@addtoreset{#1}{#2}}}

如果\c@baz不存在,则会引发错误,因为我们试图将foo不存在的计数器添加到重置列表中。否则\@addtoreset{foo}{baz}执行,这实际上会附加foo到重置列表中baz

注意\thefoo如果出现可选参数,就会发生重新定义。

如果您希望的表示foo也依赖于baz,则需要明确地告诉它;例如

\newcounter{foo}[baz]
\renewcomand{\thefoo}{\thebaz.\arabic{foo}}

LaTeX 并不具备千里眼的能力,而且也没有声称自己具备这种能力。

为什么\newtheorem{foo}{Foo}[baz]实际执行

\renewcommand{\thefoo}{\thebaz.\arabic{foo}}

而不是?因为大多数情况下人们想要定理表明了定理所服从的计数器的值。最常见的调用是

\newtheorem{theorem}{Theorem}[section]

人们普遍认为定理的编号方式如下<section>.<theorem>

我认为你不应该做那么复杂的事,只要记住发出适当的\renewcommand{\thefoo}{...}指令就足够了。

为了实现最大的灵活性,使用键值系统可能有一个更好的定义:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\NewCounter}{mO{}}
 {
  \group_begin: % to not clutter the values
  \keys_set:nn { ysalmon/newcounter } { #2 }
  \tl_if_empty:NTF \l_ysalmon_newcounter_within_tl
   {
    \newcounter{#1}
    \cs_gset:cpx { the#1 }
     {
      \exp_not:c { \l_ysalmon_newcounter_repr_tl }
      { #1 }
     }
   }
   {
    \newcounter{#1}[\l_ysalmon_newcounter_within_tl]
    \cs_gset:cpx { the#1 }
     {
      \bool_if:NT \l_ysalmon_newcounter_prefix_bool
       {
        \exp_not:c { the \l_ysalmon_newcounter_within_tl }
        \exp_not:V \l_ysalmon_newcounter_sep_tl
       }
      \exp_not:c { \l_ysalmon_newcounter_repr_tl }
      { #1 }
     }
   }
  \clist_map_inline:Nn \l_ysalmon_newcounter_also_clist
   {
    \counterwithin*{#1}{##1}
   }
  \tl_gset_eq:cN { p@#1 } \l_ysalmon_newcounter_prefix_tl
  \group_end:
 }

\keys_define:nn { ysalmon/newcounter }
 {
  within  .tl_set:N    = \l_ysalmon_newcounter_within_tl,
  within* .code:n      = \keys_set:nn { ysalmon/newcounter } { whithin=#1 }
                         \bool_set_false:N \l_ysalmon_newcounter_prefix_bool,
  also    .clist_set:N = \l_ysalmon_newcounter_also_clist,
  sep     .tl_set:N    = \l_ysalmon_newcounter_sep_tl,
  sep     .initial:n   = .,
  repr    .tl_set:N    = \l_ysalmon_newcounter_repr_tl,
  repr    .initial:n   = arabic,
  prefix  .tl_set:N    = \l_ysalmon_newcounter_prefix_tl,
 }
\bool_new:N \l_ysalmon_newcounter_prefix_bool
\bool_set_true:N \l_ysalmon_newcounter_prefix_bool

\ExplSyntaxOff

\NewCounter{fooA}

\show\thefooA

\NewCounter{fooB}[
  repr=Roman,
]

\show\thefooB

\NewCounter{fooC}[
  repr=roman,
  sep=-,
  within=fooA
]

\show\thefooC

\NewCounter{fooD}[
  within*=fooB,
  also={section,subsection},
]

\show\thefooD

我们定义四个计数器并展示它们的表示。键

  1. within指定当前计数器应从属于的计数器,并\the<counter>以该计数器的表示形式作为前缀

  2. within*相同,但没有在表示中添加前缀

  3. also指定当前计数器应从属于的可选计数器列表(不添加表示)

  4. reprarabic应为、romanRomanalph、之一(或您定义的其他计数器表示Alphfnsymbol

  5. sep是“内部”计数器表示和当前计数器表示之间的分隔符

  6. prefix\p@<counter>

这是控制台输出:

> \thefooA=\long macro:
->\arabic {fooA}.
l.51 \show\thefooA

? 
> \thefooB=\long macro:
->\Roman {fooB}.
l.57 \show\thefooB

? 
> \thefooC=\long macro:
->\thefooA -\roman {fooC}.
l.65 \show\thefooC

? 
> \thefooD=\long macro:
->\arabic {fooD}.
l.71 \show\thefooD

答案2

\thecounter我认为我找到了一个解决方案,可以让我使用以下界面进行适当的设置

\edef\@temp{\noexpand\setthe{question}\resetlevel}\@temp

但是我还不知道这与 hyperref 有何关系。

的定义\setthe

\def\setthe#1{\@ifnextchar[{\setthei{#1}}{}}
\def\setthei#1[#2]{%
\expandafter\let\expandafter\@temp\csname the#1\endcsname
\toks@\expandafter{\@temp}%
\expandafter\xdef\csname the#1\endcsname{\expandafter\noexpand\csname the#2\endcsname\@thmcountersep\the\toks@}}

难点在于获取的当前含义以\csname the#1\endcsname对其进行升级,但又不完全扩展它(因为它会在文档开头扩展为 0,并且不会使用计数器值进行更新)。

\@thmcountersep在我的定义中是因为我想显示像定理数这样的值;它可以被替换为例如一个点。

相关内容