0

抱歉标题想不出更好的了。

这是我的问题:

我试图仅在用户点击空格时更改 proc 内的变量。proc 使用 after 循环,因此如果用户希望更多地点击空间,那么一旦变量将增加。

以下是我所知道的:

有很多方法可以解决这个问题。您可以在 proc 中传递变量,您可以将变量与 global 或 upvar 链接和/或如果您在命名空间中,那么您可以使用变量。但似乎与我合作的唯一一个是全球性的。我有一种感觉,这是因为 global 建立了一个链接,但如果那是真的,那么变量也应该起作用,对吧?

这是我的测试代码:

proc test1 {} {
    global testing
    bind . <Key-a> {incr testing}
    puts $testing
    puts "test2"
    after 100 test2
}


namespace eval test2 {
    variable testing 0
    namespace export {[a-z]*}
    proc of1 {} {
            variable testing
            bind . <Key-a> {incr testing}
            puts $testing
            after 100 test3::of1
    }
}


proc test3 {testing} {
     bind . <Key-a> {incr testing}
     puts $testing
     puts "test4"
     after 100 test4 $testing
 } 
set testing 0
#test1 
test2::of1
#test3 0
grid .c 

附带问题:

为什么在全局命名空间中我们使用 set 和 global 而在命名空间中我们使用变量(似乎在一个命令中设置和执行全局)。他们似乎在不同的命名空间做同样的工作?

4

1 回答 1

1

回调中的变量

命令注册的脚本bind——也包括after事件和fileevent回调之类的——在全局范围内进行评估,因为它们可能在定义它们的过程返回很久之后被调用;Tcl 不进行范围捕获(这实际上是一个非常复杂的功能,因此除非有人编写大量代码,否则它不太可能很快出现)。这意味着您希望您的过程注意到更改的变量必须具有全局范围。

但是,就本次讨论而言,命名空间变量可以作为全局变量,因为它们可以从全局上下文中命名(真正的局部变量不是)。这意味着我们可以通过多种方式来构建一个从bind-defined 回调访问命名空间变量的脚本。这是更好的之一:

bind . <Key-a> [namespace code {incr testing}]

这实际上与此相同:

bind . <Key-a> [list namespace eval [namespace current] {incr testing}]

(在此示例中存在一些无关紧要的严格差异。)

进行回调的另一种方法是:

bind . <Key-a> [list incr [namespace which -variable testing]]

在这种情况下,这将很像:

bind . <Key-a> [list incr [namespace current]::testing]

如果事情变得比这个玩具示例更复杂,是时候停止直接在绑定脚本中更新变量,而是编写一个帮助程序。这总是把事情简化了很多。或者使用类/对象来封装细节。


命令:为什么以及在variable哪里使用它

为什么在我们使用的全局命名空间中setglobal而在namespace我们使用的时候variable(这似乎setglobal在一个命令中执行)。他们似乎在不同的命名空间做同样的工作?

这是个好问题。实际上,globaldoes 很像upvar #0(变量名加倍),set是一个基本的变量访问命令。它们是您可以在任何您想要它们的行为时经常使用的命令。

命令比较variable陌生。它的作用是三方面的:

  1. 如果在命名空间上下文中调用并且该变量在该命名空间中不存在,它会以存在但未设置的状态创建该变量。
  2. 如果在带有局部变量的上下文中调用,它将带有名称的局部变量(在剥离所有内容直到最后一个名称空间分隔符之后)链接到带有名称的名称空间变量(如果有限定符,则使用提供的整个名称,并解析非相对于当前上下文命名空间的绝对名称)。这强制命名空间变量以存在但未设置的状态存在。
  3. 如果给定了一个值,则命名空间变量被设置为该值。这摆脱了现在但不安的感觉。

重要的行为实际上是创建存在但未设置的状态,因为否则您最终可能会在命名空间中使用set(or array set) 转义该命名空间并使用全局变量,但并非总是如此。这完全取决于解析变量的代码的确切行为,这非常棘手。这很难正确解释,也很难证明是合理的。它是许多彻底错误的原因,而且绝对不比可怕的错误功能更好。

初始值的设置不过是一根棒棒糖;您可以改为set直接放置而不会产生不良影响。更重要的是,它禁止使用variable从命名空间中提取多个变量,除非您将它们设置为已知值;非常适合初始化,但不适用于其他用途。(如果你没有猜到,我认为这是 Tcl 的一个区域,在 Tcl 8.0 中引入接口时,接口出现了相当严重的错误。这一点都不好。)

关键的收获是:

  • 始终variable在命名空间中使用来声明变量,因为这是确保语义可预测的唯一方法。然后,您可以以任何您想要的方式初始化它们。(如果您要创建数组,则必须这样做。)

完全限定的变量名没有与之相关的疯狂。在这种情况下,Tcl 总是知道如何查找您命名的东西。

于 2013-10-19T20:39:23.673 回答