TclOO:将变量声明为 "class" 级别或在构造函数中的区别
TclOO: Difference between declaring variable as "class" level or in constructor
我对在哪里使用 variable myVar
以及何时需要使用 my variable myVar
感到困惑
如果我这样定义一个class,就没有错误:
oo::class create Foo {
variable bar
constructor {input} {
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
然后
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
84
但是如果我将 variable bar
移动到构造函数中:
oo::class create Foo {
constructor {input} {
variable bar
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
然后
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
can't read "bar": no such variable
这可以通过在 twoBar
方法中添加 my variable bar
来解决。
怎么回事?
在方法内部,variable
只是您所知道的常规Tcl命令,当前命名空间是实例命名空间(每个对象都有)。
在声明级别,variable
是不同的。(完全是不同的命令。)它配置了变量解析器 为配置的实体(示例中的 class Foo
)设置一个变量名,如果在本地(方法)范围内找不到,将在实例命名空间中查找;解析器实际上会对变量名列表执行此操作。变量解析器适用于 class 定义的所有方法——但 not 它的 subclasses 或 superclasses (我在另一个上试过方式;它糟透了)——或者实例如果在实例定义的上下文中。
在您的示例中,这意味着:
- 在第一种情况下,构造函数和
twoBar
方法都有 bar
实际上引用变量 ::oo::Obj12::bar
(在 TclOO 实现中关于命名空间名称的通常假设下) .
- 在第二种情况下,虽然
bar
在构造函数中引用::oo::Obj12::bar
(从技术上讲,仅在调用variable
之后),在twoBar
方法中bar
仅引用您从未设置的 local 变量。这意味着从 bar
读取失败(因为变量读取的标准语义)。
在该方法中调用 my variable bar
与 variable bar
具有相同的效果。不同的是,一个可以提升为可从对象外部调用,另一个可以一次性绑定和设置多个变量;两者都是不常见的用途,但根本不同。
在字节码级别,两个 twoBar
方法之间唯一明显的区别是(在第一个版本中):
% tcl::unsupported::disassemble method Foo twoBar
ByteCode 0x0x7ff5a2845810, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n return [expr {2 * $bar}]\n ..."
Cmds 2, src 38, inst 6, litObjs 1, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x7ff5a2835f10, refCt 1, args 0, compiled locals 1
slot 0, scalar, resolved, "bar"
Commands 2:
1: pc 0-5, src 9-32 2: pc 0-4, src 17-31
Command 1: "return [expr {2 * $bar}]..."
Command 2: "expr {2 * $bar}..."
(0) push1 0 # "2"
(2) loadScalar1 %v0 # var "bar"
(4) mult
(5) done
变量bar
(在插槽 0 中)被标记为“resolved
”(它在幕后进行恶作剧)。它们在其他方面完全相同。构造函数在命令序列上有很大差异,因此比较起来很棘手,但我们仍然可以看到那里发生了什么。
% tcl::unsupported::disassemble constructor Foo
ByteCode 0x0x7ff5a2845c10, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n set bar $input\n ..."
Cmds 1, src 28, inst 5, litObjs 0, aux 0, stkDepth 1, code/src 0.00
Proc 0x0x7ff5a2835d90, refCt 1, args 1, compiled locals 2
slot 0, scalar, arg, "input"
slot 1, scalar, resolved, "bar"
Commands 1:
1: pc 0-3, src 9-22
Command 1: "set bar $input..."
(0) loadScalar1 %v0 # var "input"
(2) storeScalar1 %v1 # var "bar"
(4) done
同样,bar
是 resolved
…
我对在哪里使用 variable myVar
以及何时需要使用 my variable myVar
如果我这样定义一个class,就没有错误:
oo::class create Foo {
variable bar
constructor {input} {
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
然后
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
84
但是如果我将 variable bar
移动到构造函数中:
oo::class create Foo {
constructor {input} {
variable bar
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
然后
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
can't read "bar": no such variable
这可以通过在 twoBar
方法中添加 my variable bar
来解决。
怎么回事?
在方法内部,variable
只是您所知道的常规Tcl命令,当前命名空间是实例命名空间(每个对象都有)。
在声明级别,variable
是不同的。(完全是不同的命令。)它配置了变量解析器 为配置的实体(示例中的 class Foo
)设置一个变量名,如果在本地(方法)范围内找不到,将在实例命名空间中查找;解析器实际上会对变量名列表执行此操作。变量解析器适用于 class 定义的所有方法——但 not 它的 subclasses 或 superclasses (我在另一个上试过方式;它糟透了)——或者实例如果在实例定义的上下文中。
在您的示例中,这意味着:
- 在第一种情况下,构造函数和
twoBar
方法都有bar
实际上引用变量::oo::Obj12::bar
(在 TclOO 实现中关于命名空间名称的通常假设下) . - 在第二种情况下,虽然
bar
在构造函数中引用::oo::Obj12::bar
(从技术上讲,仅在调用variable
之后),在twoBar
方法中bar
仅引用您从未设置的 local 变量。这意味着从bar
读取失败(因为变量读取的标准语义)。
在该方法中调用 my variable bar
与 variable bar
具有相同的效果。不同的是,一个可以提升为可从对象外部调用,另一个可以一次性绑定和设置多个变量;两者都是不常见的用途,但根本不同。
在字节码级别,两个 twoBar
方法之间唯一明显的区别是(在第一个版本中):
% tcl::unsupported::disassemble method Foo twoBar
ByteCode 0x0x7ff5a2845810, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n return [expr {2 * $bar}]\n ..."
Cmds 2, src 38, inst 6, litObjs 1, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x7ff5a2835f10, refCt 1, args 0, compiled locals 1
slot 0, scalar, resolved, "bar"
Commands 2:
1: pc 0-5, src 9-32 2: pc 0-4, src 17-31
Command 1: "return [expr {2 * $bar}]..."
Command 2: "expr {2 * $bar}..."
(0) push1 0 # "2"
(2) loadScalar1 %v0 # var "bar"
(4) mult
(5) done
变量bar
(在插槽 0 中)被标记为“resolved
”(它在幕后进行恶作剧)。它们在其他方面完全相同。构造函数在命令序列上有很大差异,因此比较起来很棘手,但我们仍然可以看到那里发生了什么。
% tcl::unsupported::disassemble constructor Foo
ByteCode 0x0x7ff5a2845c10, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n set bar $input\n ..."
Cmds 1, src 28, inst 5, litObjs 0, aux 0, stkDepth 1, code/src 0.00
Proc 0x0x7ff5a2835d90, refCt 1, args 1, compiled locals 2
slot 0, scalar, arg, "input"
slot 1, scalar, resolved, "bar"
Commands 1:
1: pc 0-3, src 9-22
Command 1: "set bar $input..."
(0) loadScalar1 %v0 # var "input"
(2) storeScalar1 %v1 # var "bar"
(4) done
同样,bar
是 resolved
…