perl 变量 my our local state

Tags:

perl 变量类型与作用域

根据作用域的不同来区分,perl有两种变量类型:

  • 词法变量(Lexical Variable) -- 词法作用域,只在当前词法作用域内可见。

  • 包变量(Package Variable) -- 全局变量,在任何位置可见

有三种创建变量的方式:

  • my -- 创建一个词法变量

  • our -- 创建一个词法变量,但实际上是包变量的别名,或者说是伪装成词法变量的包变量

  • 直接使用 -- 不加任何声明的使用一个变量,将创建一个包变量

下面我们详细说一下这几种方式

直接使用 -- 包变量

$foo = 1;

在perl早期的时候,大家都是这么定义,使用变量的。

这么定义变量$foo将会是全局变量,也就是在程序的任何位置都能访问到它。

这样当随着代码规模的增长,会引起一个严重的问题,变量污染,比如:

$foo = 1; # $foo -> 1
f();
          # $foo -> 2
sub f {
   $foo = 2;
}

如果不小心命名,则很容易在某个意想不到的位置,变量$foo被赋值,出现bug。

所以 perl5 引进了strict 和warnings 两个pragma,在代码上加上use strict后,所有没有

直接声明而使用的变量都会报错。

所以现在这种用法已经废弃了,在一些教程书中会这么开始教学,但这种用法在实际代码中是绝对不建议再使用的。

取而代之的是 my

my -- 词法变量

my $foo = 1;

my 的出现,极大的解决了变量污染的问题,因为my所声明的变量是 -- 局部变量,词法作用域。

局部变量的概念,比较好理解,比如:

my $foo = 1; # $foo -> 1
f();
             # $foo -> 1
sub f {
   my $foo = 2 # $foo -> 2
}

在函数f中的$foo 之在局部范围内起作用,出了函数之后,就不再起作用,所以也就不会影响到外部的$foo

需要理解的是这里的词法作用域,词法作用域限制了局部变量的名字生存的范围,这里一个词法作用域,基本上就是一个BLOCK,也就是一个花括号阔住的范围。

所以,只要有一个BLOCK就会建立一个词法作用域,比如:

my $foo = 1; # $foo -> 1
f();
             # $foo -> 1
sub f {
    my $foo = 2 # $foo -> 2
    if ( 1 ) {
        my $foo = 3 # $foo -> 3
    }
}

这里在函数f内部的if语句中,建立一个新的词法作用域,同样对这里的$foo赋值不会影响到外部的$foo

my基本上已经够用了,然而由于之前提到的strict 和warnings ,导致不能使用全局变量。

因此出现了our

our -- 伪装成词法变量的包变量

our $foo = 1;

our 的出现解决了不能使用全局变量的问题,our所声明的变量是全局变量的词法作用域的别名。

意思就是,这里$foo是$main::foo的别名,如果是在模块MyModule中声明的话,就是$MyModule::foo.

但是它同时又是词法作用域的,所以在词法作用域范围外无法访问到它,只能通过模块变量名$MyModule::foo来访问

可以说 our 所声明的变量是伪装成词法变量的全局变量。

举个例子:

package A;

{
    our $foo = 1;
    print $foo;     # 1
}
print $foo;         # error
print $A::foo;      # 1

package B;
print $A::foo;      # 1

这里在花括号之外将无法访问到$foo, 但能通过模块变量名来访问 $A::foo

在模块B中,也能通过$A::foo来访问模块A所定义的变量。

local -- 包变量的临时赋值

local $foo = 1;

local 并不创建一个新的变量,而是对已有的包变量临时赋予一个值,在退出当前scope后将原来的值还回去。

$foo = 1;                 # $foo -> 1
{
    local $foo = 2;       # $foo -> 2  将原先的$foo藏起来,赋予一个临时的值
}
$foo                      # $foo -> 1  将藏起来的值还回去

看起来有点像my,local确实是在my出现以前的解决方案,在my出来之后,使用的情形也就不多了。

比较有意思的是,local是动态作用域,什么意思呢? 我们比较一下下面两个代码:

my $foo = 1;
{
    my $foo = 2;
    f();
}
sub f {
    print "foo is $foo";
}

结果是

foo is 1

因为词法作用域的$foo对函数f也是不可见的,函数f看到的是在最外层的$foo,而local就不同了

$foo = 1;
{
    local $foo = 2;
    f();
}
sub f {
    print "foo is $foo";
}

结果是

foo is 2

这就是动态作用域,对一般的代码来说,local的适用范围并不广,因为本身全局变量就是不提倡使用的,而local只对全局变量起作用,

可能有些时候你需要对内建的全局变量比如 $/, $_ 临时赋值,这时候会用到local。

state -- 词法变量,静态变量

state $foo = 1;

state比较好理解,它就像c中的静态变量一样,只在第一次调用时候创建该变量,之后都是操作的同一个变量

state也是词法变量,所以只在定义该变量的词法作用域中有效,举个例子:

sub count {
    state $counter = 0;
    $counter++;
    return $counter;
}
say count();
say count();
say count();

总结

因为perl的向下兼容性做的非常好,所以perl也抗了很多历史包袱,比如这里提到的不声明直接使用的全局变量。

现在提倡的用法是变量都用my声明,在需要全局包变量的情况下,用our声明,所有代码前都要加上use strict 和use warnings

这两个pragma, 在某些特定的时候会需要local 和 static 。