当 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”将变量局部变量更改为过程“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”表示全局范围。 这样您可以绑定到全局变量,而原始示例中的过程仅在全局范围内执行时才会绑定到全局变量。