3
7
2015
0

Tcl 现代方法:变量与作用域

Tcl 是个很老很老的语言了,而且最让人叹惋的是,这语言一开始就饱受关注,然后就被广泛使用了,从此作出大的改动已经不太可能了。变量与作用域问题就是 Tcl 时代限制在这方面的体现。

Tcl 从本质上说,是个动态作用域语言,下面的代码会报错:

set a 100
proc foo {} {
    puts $a ;# undefined variable 'a'
}

然而,Tcl 又有一大堆奇妙的东西,可以让你访问那些变量:

set x 50

proc bar {_a} {
    global x
    upvar $_a a
    set a [expr $a+$x]
}

proc foo {} {
    set a 100
    bar a
    puts $a ;# 输出 150
}

这实际上就是陈梓瀚嘲笑过的 pass by name。bar 里面用到了两个特殊的工具:global 和 upvar,可以访问另一个 level 的变量。可以注意到,这里的 bar 把 foo 的局部变量修改了,我们传递的不是 $a (a 的值),而是 "a" (a 这个变量的名称),而 bar 使用 upvar 将 a 这个变量绑定到 bar 的局部变量名称 a(不应与 foo 的 a 混淆,实际上这里可以叫任何名字),从而实现了完全接管 foo 的 a 的目的。这里的 global 可以看作 upvar 的一个语法糖,把全局的一个变量 x 绑定到本地变量 x。

我们已经知道,如果 global 的对象变量不存在,则会创建一个变量;同理,upvar 也会这样做。

set x 10

proc foo {} {
    global x y
    set y 10
    expr $x+$y
}

proc bar {} {
    upvar g local
    set g 10
    return $g
}

这可以与 C 语言类比,在 C 中要让另外一个函数修改一个变量,一般使用 & 传递指针,如 &a,在 Tcl 中也类似,只不过用的是单纯的一个名字。

这样,我们看到,Tcl 居然好像也可以看作有一点词法作用域(虽然需要你手动做很多事情)。除了访问另外一个函数中的局部变量,我们也可以直接使用命名空间:

set x 50

proc foo {} {
    puts $::x
}

此处 :: 表示全局。

传统上,创建与读写变量统一使用 set,在读取全局变量时使用 global,但我们既然在讲现代方法,就不能囿于上古做法。

随着程序的增大,必然出现了组织代码的需求,命名空间随即引入,同时引入了 variable 命令。我们很快会注意到 variable 和「变量」有密切联系,同时会自然地生发问题:「variable 和 set 有何区别?」

首先看一个示例,假设我们现在需要在代码中读取应用程序名称,很自然地我们使用了命名空间。

namespace eval Config {
    set AppName "Demo"
}

这是我们一开始会想到的做法。实际上 Config::AppName 也可以访问,这没什么问题。不过,这里的 set 看上去不像是定义,而像是普通的程序逻辑,所以可以使用 variable 替代之。

namespace eval Config {
    variable AppName "Demo"
}

这样看上去就比较漂亮了(个人看法)。同时,variable 还有 set 绝对无法办到的事情,这个用途与 upvar 和 global 类似:

proc Config::foo {} {
    variable AppName
    puts $AppName ;# 输出 Demo
}

也就是说,variable 可以把一个命名空间的变量引入到当前的作用域中,默认情况下是当前函数所在的命名空间,如此处的 Config。当然,和 global 一样,variable 也能直接从函数里面创建外面命名空间的变量。

和普通命名空间不同,在 TclOO 中,成员变量无需多次 variable:

oo::class create CTest {
    variable m_hello
    constructor {} {
        variable m_hello "Hello!" ;# 初始化,这里也可以使用 set
    }
    method foo {} {
        puts $m_hello ;# 注意这里没有使用 variable
    }
}

总的来说,variable 算是 upvar 和 set 的杂交产物。

现代 Tcl 最佳实践:

  1. 尽量不使用 upvar 和 global
  2. 如大量引用全局变量,使用 global;否则使用 ::
  3. 如大量引用命名空间变量,使用 variable;否则使用 quantified name
  4. 定义命名空间变量时尽量使用 variable,除非是程序逻辑
  5. 在类中不要使用 variable,仅用来定义和初始化(初始化也可以使用 variable)
Category: 技术 | Tags: tcl tcl 现代方法 | Read Count: 1592

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

Powered by Chito | Hosted at is-Programmer | Theme: Aeros 2.0 by TheBuckmaker.com