upvar命令如何在TCL中工作?

时间:2012-06-15 09:23:21

标签: tcl upvar

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

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

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

4 个答案:

答案 0 :(得分:15)

当您调用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的命令作为一个过程,该过程将有两个必需的形式参数,并且这些参数被称为name1和{{1} }。

第二行表示我们在调用者中绑定了一个变量名(name2级别指示符来自我之前的解释是可选的,但在惯用代码中强烈建议)由{{1}给出变量(即过程的第一个参数)到局部变量1。从此以后,对该局部变量的所有访问(直到堆栈帧生命的结束)将实际对调用者中的绑定变量执行。

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

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

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

除非第二个参数命名包含Domset的变量,否则调用此命令的最终结果实际上几乎没有任何内容。这太令人困惑了。当然,令人困惑的位实际上是带有变量第一个参数的时髦Ronalod。 (这几乎总是一个坏主意;它是坏代码气味的一个指标。)如果你使用了它,事情本来会更简单:

Dom

在这种情况下,set耦合到的变量(即,由过程的第二个参数命名的变量)将被设置为set Dom "Dom" ;变量实际上是通过引用传递的。额外的Dom会带来巨大的变化!

答案 1 :(得分:1)

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

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

现在你的过程确实没有做任何事情。如果按如下方式修改最后一行,则更有意义:

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

答案 2 :(得分:1)

当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”将变量local更改为过程“bar”。

因此,为了使意图更加清晰,许多人更喜欢始终指定堆栈帧upvar应该“爬上”的数量,例如upvar 1 $varName v与{{1}相同但更清楚。

另一个有用的应用是指 local 变量,通过指定零堆栈级别来爬升 - 这个技巧有时可以更方便地访问数组中的变量:

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 } } 也理解使用“#”前缀指定的堆栈帧的绝对数量,“#0”表示全局范围。 那样你可以绑定到一个全局变量,而原始例子中的过程只有在全局范围内执行时才会绑定到全局变量。

答案 3 :(得分:1)

tcl中的upvar用于定义将在执行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 Dom

所以我可以这样做:

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,它就不起作用。

我希望这对你有用。