首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在TCL中,upvar命令是如何工作的?

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

Stack Overflow用户
提问于 2012-06-15 17:23:21
回答 4查看 24.9K关注 0票数 13

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

代码语言:javascript
复制
proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-06-15 21:07:53

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

那么让我们来看看你的代码到底在做什么:

代码语言:javascript
复制
proc tamp {name1 name2} {
    upvar $name1 Ronalod
    upvar $name2 Dom 
    set $Dom "Dom"
}

第一行说明我们正在创建一个名为tamp的命令作为一个过程,该过程将有两个强制形式参数,这些参数分别称为name1name2

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

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

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

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

除非第二个参数命名一个包含RonalodDom的变量,否则调用此命令的最终结果实际上几乎为零。这很让人困惑。当然,令人困惑的是带有变量第一个参数的时髦的set。(这几乎总是一个坏主意;它是坏代码气味的指示器。)如果您使用下面的代码,事情就会简单得多:

代码语言:javascript
复制
set Dom "Dom"

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

票数 18
EN

Stack Overflow用户

发布于 2012-06-15 18:18:48

name1和name2不存在于调用作用域中-它们只是您的proc的参数。例如,您可以按如下方式调用proc:

代码语言:javascript
复制
% set first "James"
James
% set last "Bond"
Bond
% tamp first last
Dom

就它而言,你的proc实际上什么也不做。如果您按如下所示修改最后一行,则会更有意义:

代码语言:javascript
复制
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中运行此示例,看看是否更有意义。

代码语言:javascript
复制
% 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
票数 3
EN

Stack Overflow用户

发布于 2012-06-15 21:19:49

当Tcl解释器进入一个用Tcl编写的过程时,它会在该过程的代码执行时创建一个该过程本地变量的特殊表。该表既可以包含“真实”局部变量,也可以包含指向其他变量的特殊“链接”。只要涉及到Tcl命令(如setunset等),这些链接就无法与“真实”变量区分开来。

这些链接是由upvar命令创建的,它能够创建到任何堆栈帧(包括全局作用域-帧0)上的任何变量的链接。

由于Tcl是高度动态的,它的变量可以随时来来去去,因此upvar链接到的变量在创建链接时可能不存在,请注意:

代码语言:javascript
复制
% 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与访问全局变量无关-这通常是使用globalvariable命令实现的;相反,upvar用于处理变量而不是值。当我们需要“就地”更改某些内容时,通常需要这样做;一个更好的示例是lappend命令,它接受包含列表的变量的名称,并将一个或多个元素附加到该列表中,从而就地更改它。为此,我们向lappend传递变量的名称,而不仅仅是列表值本身。现在将其与linsert命令进行比较,该命令接受一个值,而不是一个变量,因此它接受一个列表并生成另一个列表。

另一件要注意的事情是,默认情况下(以双参数形式),upvar链接到堆栈上一层具有指定名称的变量,而不是全局变量。我是说,你可以这样做:

代码语言:javascript
复制
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相同,但更清晰。

另一个有用的应用是引用局部变量,通过指定要向上攀升的零堆栈级别-这个技巧有时对于更方便地访问数组中的变量很有用:

代码语言:javascript
复制
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”表示全局作用域。这样,您可以绑定到全局变量,而原始示例中的过程如果在全局作用域中执行,则仅绑定到全局变量。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11047931

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档