核心是调用栈(call stack frame)的访问。
stack frame可以理解为程序执行的环境(context)。当调用一个函数时,可以认为是在当前的frame里新建了一个stack frame,函数在这个新建的frame里执行,默认情况下它能访问到的变量也局限于这个frame。要访问这个frame之外的变量,有两种思路。
这就要告诉程序要访问某个特定frame的位置。比如下面的例子。
其中#0, #1 是stack frame绝对位置。#0表示最顶层,也就是所谓”全局“那一层。第三行,第四行的1,2表示当前frame的上一层frame,和再上一层frame。上面的例子都表示在指定的stack frame时执行相应的命令,其中变量"var_name"也应该是声明在相应的frame里的。
/××××××××××××××××××××××××××××××××××××××××××××××
//gongxing:这里说得很透彻了,我很容易联想到upvar语法:
//upvar ?level? otherVar myVar ?otherVar myVar ...?
//里的level参数跟上面一样
//1:表示当前frame的上一层frame
//2:表示再上一层frame。
//所以编写下面代码验证
××××××××××××××××××××××××××××××××××××××××××××××/
proc three {y} {
upvar 1 $y z ;# tie the calling value to variable z
upvar 2 x a ;# Tie variable x two levels up to a
puts "three: Z: $z A: $a" ;# Output the values, just to confirm
set z 1 ;# Set z, the passed variable to 1;
set a 2 ;# Set x, two layers up to 2;
}
proc two {y} {
upvar $y z ;# This ties the calling value to variable z
puts "two: Z: $z" ;# Output that value, to check it is 5
three z ;# call proc two, which will change the value
}
proc one {y} {
set x "ok"
two y
puts $x
}
set y 5
one $y
//gongxing:y和z是层层引用,而x和a是上一层的上一层引用(蓝色部分),比对结果,three函数里的a引用到了one里的x,并
//改变了x的值
"uplevel"的用法简单来说也就是如此。"upvar"则是把指定frame里的变量拿到当前frame里来用。比如:
通过这样的声明之后,当前frame里访问变量"var_name_inside"实质上就是访问上一层frame里的变量"var_name_outside"。这跟C语言中的引用很类似。
说到这里,再看下面两条语句:
它们是不是有着相同的作用呢。但global很大程度上要求你必须知道变量的名字,而upvar则不需要。比如自己写一个printvar的命令的话。
这个例子可以很好的说明upvar的妙处来。
upvar的用法
upvar 很象c语言的引用传参,我用一个例子说明
set a 1 ;#定义变量a, 并且值设为1
proc test {b} {
upvar $b mya
puts $b
puts $mya
}
test a ;#调用函数 test
a ;#参数b的值为a(变量名)
1 ;#由于upvar 使mya(变量名)指向a(变量名)指向的同一个变量,mya的为a的值
upvar使的在函数内部可以更改函数外部的变量的值
注意:Tcl和其他的script语言的不同之处之一,Tcl要区分全局变量和局部变量。
level就是堆栈的深度,说简单一点,就是函数调用的层次,举例子说明
proc test_2 {b} {
puts [info level]
}
proc test_1 {a} {
puts [info level]
test_2 b
}
test_1 a
输出是
1
2
upvar中的level表示向上level层,据例子说明
set a aa
set b bb
proc test_2 {a} {
upvar 2 b myb ;# test_2被调用,它的层是2,要访问0层的b,需要倒退2层
}
proc test_1 {b} {
upvar a mya ;#缺省为1
test_2 sdlfld ;
}
test_1 a ;#call the test_1 proc
在 Tcl 中,Tcl 支持使用命名空间 (namespace) 的概念, namespace 的概念是不同指令与参数的集合, 你可以在不同的 namespace 中使用名字相同的程序或是变量而不至于造成混淆. 每次 Tcl 的直译器(Interpreter) 碰到一个新的程序 (procdure) 或是新的命名空间宣告 (Ex: "namespace eval" , "namesapce inscope"), Interpreter 就会把一个新的 "call frame" 塞进解译器自己的堆栈(call stack) (你可以参考 Tcl 的原始码, 找到相关的Tcl_PushCallFrame, Tcl_PopCallFrame, TclGetFrame. Source code is the best document)
我们不要挖的太深, 先看看表面的东西. 用实例来说明会容易消化点. :)
#!/usr/bin/tcl
set i 0
proc loop {} {
global i
puts "loop 的 level: [info level]";
if { $i < 256 } {
incr i;
loop;
}
}
puts "预设的 global namesapce level: [info level]"
loop
这支脚本中我们使用 [ info level ] 取得目前的 level 层次. Tcl 中的 global 命名空间是所有的 Tcl 程序中预设的. 因此他是的层次是 "0". 每次loop 这支脚本被呼叫的时候, 他的层次就会被加一, 这完全是因为要作命名空间区隔的关系. 这个机制在你使用一个命名空间的时候也会产生作用.
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
puts [info level]
}
}
foo::bar
那么这样的机制跟 uplevel 有甚么关系呢? uplevel 是用来在不同的 "stack frame" 中执行脚本用的. (我们 knowledge based 中的 tcl 文件翻译的太怪了? "在一个不同的环境下执行某个script" :p )你可以使用 uplevel 在不同层次的命名空间动态切换.
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
uplevel 1 { set name "Kid"; }
set name "blah...";
}
}
set name "Rex Tsai"
puts $name
foo::bar
puts $name
执行后, 你可以发现 name 被改为 Kid.这是因为 uplevel 上到高一层的命名空间. 并修改了 name 这个变量. 请注意, 其中的 set name "blah..." 跟uplevel 中的 name 是不同两个世界的唷!
另外一个有趣的指令是 upvar,upvar 可以让你产生一个链接连到别的堆栈框架. 他是使用 pass by reference 的方式连结, 因此如果你直接更动了变量,就会直接更动原本的变量. (请参考 Tcl 的原始码, MakeUpvar Tcl_UpVar Tcl_UpVar2 )
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
upvar 1 name j # 或是 upvar #0 name j
set j "Kid"
}
}
set name "Rex Tsai"
puts $name
foo::bar
puts $name
upvar 与 uplevel 都必须指定你想使用的命名空间层级, 如果不指定, 他会使用上一层, 默认值是 1. 另外, 也可以使用抽象的层级号码, 他的方式是以 #开头, 后面接一个层级数字. 假设我们有这样的一只脚本
#!/usr/bin/tcl
proc foobar {} { puts "foobar:\t[info level]"; foo; };
proc foo {} { puts "foo:\t[info level]"; bar; };
proc bar {} { puts "bar:\t[info level]" };
puts "global:\t[info level]"
foobar
其实他的层级号码是像这样的.
global: 0
foobar: 1
foo: 2
bar: 3
因此预设的 global namesapce 的抽象层级号码是 0 , foobar 抽象层级号码是 1. 如果我们使用指定号码的方式存取其它堆栈框架, 这些数字应该为相对应的, 例如在 bar 中想存取 foobar层级, foobar 是 bar 的上面两层, 因此应该使用 uplevel 2 { body; } 来撰写. 懂了吗?
说了那么多, 到底这个玩意能作些甚么, 这里举出一个范例. 在 Tcl 中, 并没有像 C 一样 static variables 的量类型. 这里是一个利用 uplevel 与upvar 实作 static variables 的 procedure. 看得懂, 你就出师了. :p
#!/usr/bin/tcl
proc static {args} {
set procName [lindex [info level [expr [info level] -1]] 0]
foreach varName $args {
uplevel 1 "upvar #0 staticvars($procName:$varName) $varName"
}
}
proc foo {} {
static name
if { ! [info exist name] } {
set name "Rex Tsai"
}
puts $name
}
foo
foo
参考:
http://www.51testing.com/html/96/240996-122821.html
https://blog.csdn.net/Augusdi/article/details/45587465?utm_source=blogxgwz8
https://blog.csdn.net/augusdi/article/details/45587479