我对 TCL 中的 upvar 命令有疑问。使用 upvar 命令,我们可以在其他过程中引用全局变量或局部变量。我看到了以下代码:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}
这个过程被称为tamp name1 name2
,并且没有在它之外定义全局变量 name1、name2,这个 upvar 在这种情况下是如何工作的?
当你调用 时upvar 1 $foo bar
,它会在调用者的作用域中找到名称在变量中的foo
变量,并将局部bar
变量作为它的别名。如果变量不存在,则在未设置状态下创建它(即,变量记录存在但没有值。实际上,实现使用 aNULL
来表示该信息,这就是为什么 Tcl 没有NULL
等价物;NULL
表示非-existence) 但仍会创建链接。(只有在局部作用域被破坏或 ifupvar
用于将局部变量指向其他东西时,它才会被拆除。)
所以让我们看看你的代码到底在做什么:
proc tamp {name1 name2} {
upvar $name1 Ronalod
upvar $name2 Dom
set $Dom "Dom"
}
第一行说我们正在创建一个称为tamp
过程的命令,该过程将有两个强制性的形式参数,并且这些参数称为name1
和name2
。
第二行说我们在调用者中绑定了一个变量名(1
我之前解释中的级别指示器是可选的,但在惯用代码中强烈建议使用),它由name1
变量(即过程的第一个参数)给出局部变量Ronalod
。此后,对该局部变量的所有访问(直到堆栈帧的生命结束)实际上都将在调用者中的绑定变量上执行。
第三行几乎相同,除了name2
(第二个参数)和Dom
(局部变量)。
第四行实际上很时髦。它读取Dom
变量以获取变量名(即,在过程调用的第二个参数中命名的变量)并将该命名变量设置为 value Dom
。请记住,在 Tcl 中您使用从变量$
中读取,而不是谈论变量。
过程调用的结果将是其主体中最后一个命令的结果(即,字面量Dom
sinceset
产生变量的内容作为其结果,它刚刚分配的值)。(最后一行完全没意思,因为它只是程序主体的结尾。)
调用这个命令的最终结果实际上几乎什么都没有,除非第二个参数命名一个包含Ronalod
or的变量Dom
。这很令人困惑。set
当然,第一个参数的变量确实令人困惑。(这几乎总是一个坏主意;它是不良代码气味的一个指标。)如果您改用它,事情会更简单:
set Dom "Dom"
在这种情况下,Dom
耦合到的变量(即,由过程的第二个参数命名的变量)将设置为Dom
; 这些变量实际上是通过引用传递的。额外的东西$
有很大的不同!
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
当 Tcl 解释器进入一个用 Tcl 编写的过程时,它会创建一个特殊的变量表,其中包含该过程的局部变量,同时它的代码正在执行。 该表既可以包含“真实”局部变量,也可以包含到其他变量的特殊“链接”。只要涉及 Tcl 命令(例如 等),此类链接就无法与“真实”变量区分开set
来unset
。
这些链接由upvar
命令创建,它能够创建指向任何堆栈帧(包括全局范围 - 帧 0)上的任何变量的链接。
由于 Tcl 是高度动态的,它的变量可以随时来来去去,因此链接到 by 的变量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
andvariable
命令来实现;相反,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
应该“向上爬”,就像 in upvar 1 $varName v
which 一样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”表示全局范围。 这样,您可以绑定到全局变量,而原始示例中的过程仅在全局范围内执行时才会绑定到全局变量。
那么 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 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
,它不起作用。
我希望这对你有用。