【问题标题】:how the upvar command works in TCL?upvar 命令在 TCL 中是如何工作的?
【发布时间】:2012-06-15 09:23:21
【问题描述】:

我对 TCL 中的 upvar 命令有疑问。使用 upvar 命令,我们可以在其他过程中引用全局变量或局部变量。我看到了以下代码:

proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

这个过程被称为tamp name1 name2,并且没有在它之外定义全局变量name1、name2,这个upvar在这种情况下是如何工作的?

【问题讨论】:

    标签: tcl upvar


    【解决方案1】:

    当您调用upvar 1 $foo bar 时,它会在调用者的作用域中找到名称在foo 变量中的变量,并将本地变量bar 设为它的别名。如果变量不存在,它创建处于未设置状态(即,变量记录存在但它没有值。实际上,实现使用NULL 来表示该信息,这就是为什么 Tcl 没有 NULL 等效项;NULL 表示不存在)但仍然创建链接。 (只有在局部作用域被破坏或upvar 用于将局部变量指向其他东西时,它才会被拆除。)

    那么让我们看看你的代码真正在做什么:

    proc tamp {name1 name2} {
        upvar $name1 Ronalod
        upvar $name2 Dom 
        set $Dom "Dom"
    }
    

    第一行说我们正在创建一个名为 tamp 的命令作为一个过程,该过程将有两个强制性的形式参数,并且这些参数称为 name1name2

    第二行说我们在调用者中绑定了一个变量名(我之前解释中的1 级别指示器是可选的,但在惯用代码中强烈建议使用),这是由name1 变量给出的(即,过程的第一个参数)到局部变量Ronalod。此后,对该局部变量的所有访问(直到堆栈帧生命周期结束)实际上都将在调用者的绑定变量上执行。

    第三行几乎相同,除了name2(第二个参数)和Dom(局部变量)。

    第四行实际上很时髦。它读取Dom 变量以获取变量名称(即,在过程调用的第二个参数中命名的变量)并将该命名变量设置为值Dom。请记住,在 Tcl 中,您使用 $ 从变量中读取,而不是谈论变量。

    过程调用的结果将是其主体中最后一个命令的结果(即文字 Dom,因为 set 产生变量的内容作为其结果,它刚刚分配的值)。 (最后一行完全没意思,因为它只是过程体的结尾。)

    调用此命令的最终结果实际上几乎什么都没有,除非第二个参数指定一个包含RonalodDom 的变量。这很令人困惑。当然,令人困惑的一点确实是带有可变第一个参数的set。 (这几乎总是一个坏主意;它是不良代码气味的一个指标。)如果您改用它,事情会更简单:

    set Dom "Dom"
    

    在这种情况下,Dom 耦合到的变量(即过程的第二个参数命名的变量)将设置为Dom;这些变量实际上是通过引用传递的。额外的$ 有很大的不同!

    【讨论】:

      【解决方案2】:

      name1 和 name2 在调用范围内不存在 - 它们只是您的 proc 的参数。例如,您可以按如下方式调用 proc:

      % set first "James"
      James
      % set last "Bond"
      Bond
      % tamp first last
      Dom
      

      就目前而言,您的 proc 确实没有做任何事情。如果将最后一行修改如下,那就更有意义了:

      proc tamp {name1 name2} {
          upvar $name1 Ronalod
          upvar $name2 Dom 
          set Dom "Dom"
      }
      % tamp first last
      Dom
      % puts $first
      James
      % puts $last
      Dom
      

      我见过的关于使用 upvar 和 uplevel 的最佳指南之一是 Rob Mayoff 的指南 http://www.dqd.com/~mayoff/notes/tcl/upvar.html


      我正在添加另一个示例,以帮助您了解 name1 和 name2 只是输入参数,不需要全局存在。在 tclsh 中运行这个例子,看看它是否更有意义。

      % proc tamp {name1 name2} {
          upvar $name1 Ronalod
          upvar $name2 Dom
          puts "Ronalod=$Ronalod, Dom=$Dom"
          set Ronalod "Brian"
          set Dom "Fenton"
          puts "NOW: Ronalod=$Ronalod, Dom=$Dom"
      }
      %
      % tamp name1 name2
      can't read "Ronalod": no such variable
      % set first "James"
      James
      % set last "Bond"
      Bond
      % tamp first last
      Ronalod=James, Dom=Bond
      NOW: Ronalod=Brian, Dom=Fenton
      % puts $first
      Brian
      % puts $last
      Fenton
      

      【讨论】:

      • 如果name1和name2只是参数,那么在proc tame中,upvar是什么意思呢?和“set name1 Ronaldo”意思一样?
      • 嗨! “upvar $name1 Ronalod”的字面意思是创建一个名为 Ronalod 的局部范围变量,该变量链接到 $name1 引用的变量。然后,对 Ronalod 的任何更改也将更改 $name1 引用的变量。你可以看到,在我的例子中,“last”的值被 set Dom “Dom”改变了。
      • 谢谢,但是在我的代码中,并没有全局变量name1、name2,所以如果你设置Dom "Dom",就意味着变量Dom被改变了,$name1引用的变量也是改变了,但在我的情况下, $name1 引用的变量不存在?
      • 没问题。我在问题中添加了另一个示例。我认为您对 name1 作为输入参数(指向 proc 外部的变量)和 name1 作为全局变量之间的区别感到困惑。将其视为一种指针......这是否有更多帮助?
      • 是的,谢谢,但是在我正在阅读的代码中,name1 和 name2 根本没有在 tamp proc 之外定义....
      【解决方案3】:

      当 Tcl 解释器进入一个用 Tcl 编写的过程时,它会在其代码正在执行时创建该过程的局部变量的特殊表该表可以包含“真实”局部变量和到其他变量的特殊“链接”。只要涉及到 Tcl 命令(例如setunset 等),此类链接就无法与“真实”变量区分开来。

      这些链接由upvar 命令创建,它能够创建指向任何堆栈帧(包括全局范围 - 帧 0)上的任何变量的链接。

      由于 Tcl 是高度动态的,它的变量可以随时来来去去,因此 upvar 链接到的变量在创建链接时可能不存在,请注意:

      % unset foo
      can't unset "foo": no such variable
      % proc test name { upvar 1 $name v; set v bar }
      % test foo
      bar
      % set foo
      bar
      

      请注意,我首先证明名为“foo”的变量不存在,然后在使用upvar 的过程中设置它(并且该变量是自动创建的),然后在过程退出后证明该变量存在。

      还要注意upvar 不是访问全局变量——这通常使用globalvariable 命令来实现;相反,upvar 用于处理变量而不是值。当我们需要“就地”改变某些东西时通常需要这样做;更好的例子之一是lappend 命令,它接受包含列表的变量的名称并将一个或多个元素附加到该列表,并在适当的位置进行更改。为此,我们传递lappend 变量的名称,而不仅仅是列表值本身。现在将其与接受值而非变量的linsert 命令进行比较,因此它接受一个列表并生成 另一个 列表。

      另外需要注意的是,默认情况下(以两个参数的形式),upvar 链接到具有指定名称的变量堆栈上一层,而不是全局变量。我的意思是,你可以这样做:

      proc foo {name value} {
        upvar $name v
        set v $value
      }
      
      proc bar {} {
        set x ""
        foo x test
        puts $x ;# will print "test"
      

      }

      在本例中,过程“foo”将变量局部变量更改为过程“bar”。

      因此,为了使意图更清晰,许多人更喜欢始终指定堆栈帧的数量 upvar 应该“向上爬”,就像在 upvar 1 $varName v 中一样,它与 upvar $varName v 相同,但更清晰。

      另一个有用的应用是引用局部变量,通过指定零堆栈级别来爬上 - 这个技巧有时对更方便地访问数组中的变量很有用:

      proc foo {} {
         set a(some_long_name) test
         upvar 0 a(some_long_name) v
         puts $v ;# prints "test"
         upvar a(some_even_more_long_name) x
         if {![info exists x]} {
           set x bar
         }
      }
      

      作为奖励,请注意upvar 还可以理解使用“#”前缀指定的堆栈帧的绝对数量,而“#0”表示全局范围。 这样您可以绑定到全局变量,而原始示例中的过程仅在全局范围内执行时才会绑定到全局变量。

      【讨论】:

        【解决方案4】:

        tcl 中的 upvar 用于定义 vars 以在 proc 执行后使用。示例:

        proc tamp {name1 name2} {
            upvar 1 $name1 Ronalod
            upvar 1 $name2 Dom 
            set $Dom "Dom"
        }
        
        #Call the proc tamp
        
        tamp $name1 $name2
        
        #Now we can use the vars Ronalod and Dom
        
        set all_string "${Ronalod}${Dom}"
        

        现在在下面的命令中输入1

        upvar 1 $name2 Dom 
        

        指出你可以使用变量的级别,例如如果我们使用2

        upvar 2 $name2 域名

        所以我可以这样做:

        proc tamp_two {name1 name2} {
              # do something ...
              tamp $name1 $name2
        }
        
        proc tamp {name1 name2} {
            upvar 2 $name1 Ronalod
            upvar 2 $name2 Dom 
            set $Dom "Dom"
        }
        
        #Call the proc tamp_two
        
        tamp_two $name1 $name2
        
        #Now we can use the vars Ronalod and Dom
        
        set all_string "${Ronalod}${Dom}"
        

        这可以通过upvar 2 实现,如果我们保留upvar 1,它就不起作用。

        希望对你有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多