upvar命令在TCL中是如何工作的?

upvar命令在TCL中是如何工作的?,tcl,upvar,Tcl,Upvar,我对TCL中的upvar命令有一个问题。使用upvar命令,我们可以在其他过程中引用全局变量或局部变量。我看到了以下代码: proc tamp {name1 name2} { upvar $name1 Ronalod upvar $name2 Dom set $Dom "Dom" } 这个过程被称为tamp name1 name2,在它之外没有定义全局变量name1、name2,这个upvar在这种情况下是如何工作的?name1和name2在调用范围中不存在-它们只是

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

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

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

name1和name2在调用范围中不存在-它们只是过程的参数。例如,您可以按如下方式调用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的最佳指南之一是robmayoff的指南


我添加了一个进一步的示例,以帮助您了解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

调用
upvar 1$foo bar
时,它会在调用者的作用域中找到名在
foo
变量中的变量,并将本地
bar
变量作为其别名。如果变量不存在,它将在未设置状态下创建(即,变量记录存在但没有值。事实上,实现使用
NULL
表示该信息,这就是为什么Tcl没有
NULL
等效项;
NULL
表示不存在),但仍然创建链接。(只有当局部作用域被销毁或使用
upvar
将局部变量指向其他对象时,它才会被删除。)

让我们看看您的代码真正在做什么:

第一行表示我们正在创建一个名为
tamp
的命令作为一个过程,该过程将有两个必需的形式参数,这些参数称为
name1
name2

第二行表示我们正在将调用方中的变量名(我前面解释的
1
级别指示器是可选的,但在惯用代码中强烈建议使用)绑定到本地变量
name1
变量(即过程的第一个参数)上。此后,对该局部变量的所有访问(直到堆栈帧的生命结束)都将在调用者中的绑定变量上实际执行

第三行几乎相同,除了
name2
(第二个参数)和
Dom
(局部变量)之外

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

过程调用的结果将是其主体中最后一个命令的结果(即,文本
Dom
,因为
set
生成变量的内容作为其结果,它刚刚分配了一个值)。(最后一行完全没有意思,因为它只是过程主体的结尾。)

调用此命令的最终结果实际上将几乎是零,除非第二个参数命名一个包含
Ronalod
Dom
的变量。这太令人困惑了。当然,令人困惑的是,第一个参数是一个变量,它是一个时髦的
set
。(这几乎总是一个坏主意;它是坏代码气味的指示器。)如果您使用此选项,事情会更简单:

set Dom "Dom"

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

当Tcl intepreter输入用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"
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}"
}

在本例中,过程“foo”更改了局部变量t
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
   }
}
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}"
upvar 1 $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}"