我对TCL中的upvar命令有疑问。使用upvar命令,我们在其他过程中引用了全局变量或局部变量。我看到了以下代码:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}
这个过程被称为tamp name1 name2
,并且没有全局变量name1,name2在它之外定义,这个upvar在这种情况下如何工作?
答案 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
,因为$
产生变量的内容作为其结果,它具有一个值刚刚分配)。 (最后一行完全没有意义,因为它只是程序正文的结尾。)
除非第二个参数命名包含Dom
或set
的变量,否则调用此命令的最终结果实际上几乎没有任何内容。这太令人困惑了。当然,令人困惑的位实际上是带有变量第一个参数的时髦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命令(例如set
,unset
等),此类链接就无法与“真实”变量区分开来。
这些链接由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
与访问全局变量无关 - 这通常使用global
和variable
命令实现;相反,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
,它就不起作用。
我希望这对你有用。