我有一个关于TCL中的upvar命令的问题。使用upvar命令,我们可以引用其他过程中的全局变量或局部变量。我看到了以下代码:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}这个过程被称为tamp name1 name2,并且在它的外部没有定义全局变量name1,name2,在这种情况下这个upvar是如何工作的?
发布于 2012-06-15 21:07:53
当您调用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和name2。
第二行说明我们在调用方中绑定了一个变量名(我前面解释的1级别指示器是可选的,但在惯用代码中强烈建议),该变量名由name1变量(即,过程的第一个参数)提供给局部变量Ronalod。此后,对该局部变量的所有访问(直到堆栈帧的生命周期结束为止)都将实际在调用方中的绑定变量上执行。
除了name2 (第二个参数)和Dom (局部变量)之外,第三行几乎相同。
第四行实际上相当时髦。它读取Dom变量以获得变量名(即,在过程调用的第二个参数中命名的变量),并将命名的变量设置为值Dom。请记住,在Tcl中,您使用$读取变量,而不是谈论变量。
过程调用的结果将是其主体中的最后一条命令的结果(即,文字Dom,因为set会将变量的内容作为其结果,这是它刚刚分配的值)。(最后一行完全没有意义,因为它只是过程体的结尾。)
除非第二个参数命名一个包含Ronalod或Dom的变量,否则调用此命令的最终结果实际上几乎为零。这很让人困惑。当然,令人困惑的是带有变量第一个参数的时髦的set。(这几乎总是一个坏主意;它是坏代码气味的指示器。)如果您使用下面的代码,事情就会简单得多:
set Dom "Dom"在这种情况下,Dom耦合到的变量(即,由过程的第二个参数命名的变量)将被设置为Dom;这些变量实际上是通过引用传递的。额外的$会带来很大的不同!
发布于 2012-06-15 18:18:48
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发布于 2012-06-15 21:19:49
当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中一样,它与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”表示全局作用域。这样,您可以绑定到全局变量,而原始示例中的过程如果在全局作用域中执行,则仅绑定到全局变量。
https://stackoverflow.com/questions/11047931
复制相似问题