Upvar会在different stack frame中创建指向变量的链接,有时称为call stack或different scope。
Upvar还用于为全局(或命名空间)变量2创建别名。但是命名空间只能由namespace eval命令创建。 proc命令创建一个新的堆栈帧。
命名空间和调用堆栈似乎是TCL naming context可以改变的两种方式。 Upvar和Uplevel可以在命名空间和调用堆栈上工作。
我做对了吗?我还没有看到调用堆栈和命名空间之间的直接比较,因此我的问题。
答案 0 :(得分:8)
不,不完全。命名空间和调用框架是非常不同的概念。命名空间是可以消除同义词歧义的名称层次结构。您的程序中可能有三个名为foo
的变量,但如果将它们放在不同的名称空间中,它们不会发生冲突。命名空间可用于变量和命令名称。使用namespace eval
创建后,在您调用namespace delete
之前,始终可以访问命名空间的内容。
调用堆栈是一系列堆栈帧。第一个堆栈帧#0始终存在。无论何时调用命令,都会创建其他堆栈帧(这主要用于用户定义过程的命令,"内置"命令遵循自己的规则)。当命令返回时,它们会再次被销毁。因此,如果您调用命令A,并且A调用命令B,并且B调用命令C,则您有一个如下所示的调用堆栈:
#3 : <C's variables>
#2 : <B's variables>
#1 : <A's variables>
#0 : <global and namespace variables>
除非使用upvar
,否则每个堆栈框架都是一个范围,只有在那里创建或导入到其中的变量才能被访问。其他一切都是隐藏的。在大多数编程语言中,可以从内部作用域自动访问外部作用域的名称,例如全局作用域。在Tcl中不是这样。
使用upvar
,您可以让命令查看其自己的堆栈帧之外的内容。例如,C可以使用upvar #0 foo bar
为全局变量bar
创建别名(foo
),或者使用upvar 1 baz qux
(注意没有#)来创建别名(qux
)用于B&#39的堆栈框架中的变量baz
。
uplevel
命令可以沿着相同的行使用,以在另一个堆栈帧中执行脚本,包括全局脚本。在执行期间,脚本可以访问该堆栈帧中的所有内容,但不包括任何其他内容,包括调用uplevel
的堆栈帧中的变量。
C还可以使用::abc::def
为命名空间变量upvar #0 ::abc::def ghi
创建别名,但不要这样做,而是使用namespace upvar ::abc def ghi
。
您可以使用upvar #0 foo foo
导入全局变量,而不是global foo
。在命名空间中定义的命令内,variable
命令可以导入在同一命名空间中定义的变量。
upvar
或uplevel
对#0(全局帧)或1(呼叫者的帧)通常很有用。使用其他帧编号容易出错,通常表明设计不佳。调用upvar 0 foo bar
为同一堆栈帧中的变量(bar
)创建别名(foo
),这可能非常有用。
正在处理的事件调用的命令使用全局级别在调用堆栈外执行。它们无法进入活动堆栈帧并访问驻留在那里的变量。
一个简单的演示:
namespace eval ::abc {
variable def 42
proc xyz {} {
variable def
}
}
set foo 1138
proc A {} {
B
}
proc B {} {
set baz 1337
C
}
proc C {} {
upvar #0 foo bar
puts $bar
upvar 1 baz qux
puts $qux
namespace upvar ::abc def ghi
puts $ghi
}