Perl 中的 作用域 指的是符号的生存期限和可见性。在 Perl 中,任何有名字的事物 (比如:变量、函数)都有作用域。作用域的制定有助于强制 封装————将相关的概念 放在一块并防止它们的泄漏。
在现代的 Perl 编程环境中,最常见的作用域形式是词法作用域。Perl 编译器在编译期解 决此类作用域。这类作用域在你 阅读 一段程序时是可见的。
要创建一个新的词法作用域,可以编写一个由大括号分隔的代码块。这个代码块可以是一个 裸块、或循环结构主体中的块、一个 eval
块、或是其他任何没有用引号引起的块:
词法作用域管理由 my
声明的变量的可见性;这些变量被称作 词法 变量。在某块内 声明的词法变量对块本身及嵌套块可见,但对此块兄弟或外层块是不可见的。因此,在如下 代码中:
…… $outer
对全部四个作用域都是可见的。$inner
在方法内部、do
代码块和 for
循环内都是可见。$do_scope
仅在 do
代码块内可见,$for_scope
仅 在 for
循环内可见。
在内层词法作用域里声明一个和外部词法作用域同名的词法变量将隐藏,或者说 遮盖 外层的词法变量:
这段程序在 Edward
之后接着打印 Jacob
。即使在单一词法作用域内重新声明一个有 着相同名称相同类型的词法变量会产生一个警告,在嵌套作用域内遮盖一个词法变量则不会; 这是一个词法遮盖的特性。
词法变量的声明有着自身的微妙性。例如,一个用作 for
循环迭代器变量的词法变量的 作用域是循环代码块 内部。它对循环体外部是不可见的:
类似地,given
语法结构在其块内部创建了 词法话题 (近似于 my $_
):
……先不管块内对 $_
的赋值。你可以显式地词法化话题,虽然这在考虑动态作用域时 更为有用。
最后,词法作用域助于构造闭包(closures)。注意不要意外地创建闭包。
在给出的作用域内,你可以用 our
关键字声明一个包变量的别名。就像 my
一样, our
强制了别名的词法作用域。完全限定名称随处可用,但是词法别名仅在自身作用 域内可见。
对 our
的最好使用就是声明那些你 不得不 有的变量,诸如 $VERSION
。
动态作用域在可见性规则上类似于词法作用域,和在编译期确定作用域相反,确定作用域 的过程沿着调用上下文发生。考虑如下例子:
这段程序由声明一个 our
变量————$scope
,和三个函数开始。它于赋值 $scope
并调用 main()
处结束。
在 main()
内,这个程序打印出 $scope
当前的值,即 outer scope
,接着用 local
局部化了这个变量。这样,符号在当前词法作用域内的可见性 连同 该符号 在此词法作用域内调用的函数内部的可见性一同被改变。因此,$scope
在 middle()
和 inner()
两者的代码体包含 main() scope
这个值。在 main()
返回后———— 在该点流程退出了那个代码块,块中包含 local
后的 $scope
,Perl 恢复了变量的 原始值。最后的 say
再次打印出 outer scope
。
虽然此变量在这些作用域内都是 可见的,但变量 值 的变化却取决于用 local
局部化和赋值操作。这个特性既狡猾又微妙,但改变那些神奇变量时还是相当有用的。
包变量和词法变量在可见性上的区别用 Perl 5 自身存储这些变量的机制来解释,就会变得 非常明显。词法变量被存放在附着于作用域的 词法板 中。每次进入到词法作用域中都需 要 Perl 来创建一个包含变量值的新的专属词法板。(这就是为什么函数可以调用自身而不会 弄坏现有同名变量的值。)
包变量的存储机制称为符号表。每个包都含有一个单独的符号表,并且每个包变量在其中占有 一个条目。你可以用 Perl 检查并修改这个符号表;这就是导入的工作原理(importing)。 这也是为什么你只能用 local
局部化全局和包变量而非词法变量。
用 local
局部化若干神奇变量的做法很常见。举例来说,$/
,输入记录分隔符,决 定了 readline
操作从一个文件句柄读入数据的量。$!
,系统错误变量,包含了最近 一次系统调用的错误号。$@
,Perl eval
错误变量,包含最近一次 eval
操作中 发生的任何错误。$|
,自动冲洗变量,决定了 Perl 是否应该在每次写操作之后,自动 冲洗当前由 select
选定的文件句柄。
这些全部是特殊的全局变量;在最窄小的作用域内用 local
局部化它们可以避免其余代 码远距离修改所用全局变量而引发的问题。
最后一种作用域类型的年纪和 Perl 5.10 一样。这是 state
关键字的作用域。状态作用域 类似词法作用域的地方在于,它声明一个词法变量,但是该变量的值只初始化 一次,随后便 一直保持:
在第一次调用计数函数的地方,$count
从未被初始化过,因此 Perl 执行赋值操作。这个 程序打印出 1
、2
和 3
。如果你把 state
改为 my
,则会打印 1
、1
、1
。
你也可以使用传入参数来初始化 state
变量的值:
虽然这段代码粗粗一读让人感觉输出应该是 2
、4
、6
,但实际上是 2
、3
和 4
。第一次对 counter
子过程的调用设置了 $count
变量。后续调用不会改变它的 值。这个行为是意料之中并如同记载的一样,但这种实现方式会导致令人惊讶的结果:
程序中计数器按预想地打印出 2
、3
和 4
,但是成为 counter()
调用的第 二个参数却依次为 two
、4
和 6
————并非因为这些整数确实变成了传递的第二 个参数而是因为第一个参数的 shift
只发生在第一次调用 counter()
的时候。
state
在创建默认值和准备缓存时非常有用,但在使用它前,请确信你已经理解了它 的初始化行为。
Hey! The above document had some coding errors, which are explained below:
- Around line 5:
-
A non-empty Z<>
- Around line 16:
-
A non-empty Z<>
- Around line 163:
-
A non-empty Z<>
- Around line 175:
-
A non-empty Z<>
- Around line 262:
-
A non-empty Z<>