perl范围声明our-my-local
2009-12-01 13:48:59 阿炯

老实说,这个东西有点折腾人,主要是书上的表述很晦涩,其实搞懂不难。
 
our,"把名字限于某个范围“,其实就是明确声明一个"全局变量",虽然是在某个模块或者函数里面定义的,外面的也可以访问,如果已经声明过了,再次用"our",表示此处用的是全局的那个,不是同名的私有或者局部变量。
 
our $PROGRAM_NAME = "waiter";
{
my  $PROGRAM_NAME = "something";
our $PROGRAM_NAME = "server"; #这里的our和外面的相同,和前句不同。
# 这里调用的代码看到的是"server"
}
# 这里执行的代码看到的仍然是"server".

my ,"把名字和值都限于限于某个范围",简单说,就是只能本层模块或者函数可以看到这个变量,高一层的或者低一层的都看不到的。
sub greeting1{
    my ($hello) = "How are you do?";
    greeting2();
}
 
sub greeting2{
    print "$hello\n";
}
$hello = "How are you doing?";
greeting2();
greeting1();
greeting2();
 
运行结果:
How are you doing?
How are you doing?
How are you doing?
--------------------------
一个 How are you do? 都没有,在greeting1中call greeting2时,greeting2看不到greeting1的私有$hello变量,只能看到外面的全局变量$hello。
 
local,"把值局限于某个范围",也有叫"动态词法范围",有点不好懂。我的理解,就是本层和本层下层的函数可以看到本层的变量,但是本层上一层的不可以。到底范围是多少,不仅取决于本层的函数,还要看下一层的程序长度和深度,所以叫"动态范围"。
 
sub greeting1{
    local ($hello) = "How are you do?";
    greeting2();
}

sub greeting2{
    print "$hello\n";
}
$hello = "How are you doing?";
greeting2();
greeting1();
greeting2();
 
运行结果:
How are you doing?
How are you do?
How are you doing?
-----------------------
跟用 my 时不一样了吧? 此时在greeting1调用greeting2时,greeting2可以看到greeting1的局部变量$hello,外部的全局变量当然就隐藏了。

my和our的区别分析

our 和 my 一样,都是对变量的声明,不过 our 声明的是包全局变量,而 my 声明的是词法变量。

经过 our 声明的变量,它会变得像一个词法变量一样,其实这也是 our 存在的目的:用来欺骗 strict pragma,使 strict 以为它是一个词法变量,其实却不是。

有一个简单的办法可以理解our:
1.就把 our 声明的变量和 my 声明的当成一样。
2.记住 our 和 my 的区别:our 声明的是一个包全局变量,因此在符号表中存储(可以通过全限定在任何地方访问),而 my 声明的是一个真正的词法变量,只能在闭合块中访问。

our $var = 1;
{
    our $var = 2;
    print $var, "\n";
}
print $var, "\n";

输出:
2
2

our 的出现有它的历史,Perl和别的语言不同,可以随便声明变量,在 Perl 4 那个时代,根本就不需要 my 什么的,随便写个名字,就是变量了。在 Perl5 中仍然如此,除非你用 my 明确声明为词法变量,否则所有的变量都是(包)全局变量,而且可以不声明直接使用。

但这样有个坏处,那就是万一不小心写错名字了,或者解符号引用的时候,字符串运算错了,都会造成很多麻烦(因为按照 Perl 5 语法,这些都是正确的,其结果就是产生一个新的变量,很显然,这不是你想要的目的。)

为了解决这些问题,在 Perl 5 中就引入了 strict 和 warnings 两个 pragma,它们的作用,就是限制变量不声明直接使用,经过它俩的限制后,所有没有声明的直接使用的变量都会报错。但是 my 声明的变量又是局部变量,local 又不能创造变量,所以就没法使用全局变量了;因此就又引入了 our,其作用就是声明一个全局变量,但是让 strict 和 warnings 以为它是词法变量,因此 our 声明的变量也是词法作用域的。但是实际上它是全局变量。

如果不使用 our,有两种办法可以创建全局变量:
1.用 no strict "vars" 临时关掉 strict pragma,声明完了再用 use strict "vars" 打开。
2.用变量的全限定名称,如 $main::var 或者 $foo::bar 这种写法。

use、package、our 三者之间无任何关系。

use 是加载一个 .pm 文件,package 是切换当前名字空间,our 是在当前名字空间中创建一个变量,如果该变量已经存在,则 our 只起到一个声明的作用。

our(或者什么修饰都没有)声明的是“包全局变量”,它的是“依附”在“包”上面的,它的存储位置是“包符号表”;my 声明的是“词法变量”,它是“依附”在“代码块”上的,它的存储位置是“代码块”的“变量标签薄”,所以词法变量不可以从代码块之外访问(除了传递引用)。但是包全局变量就不同了,用全限定就可以访问。


本节转自骏马金龙博客,可以使用local和our两个修饰符修饰变量,以下是my、local、our的区别:

1.my:将变量限定在一个代码块中。对于包范围内的my变量,它是包私有的,其它包无法访问。my实现的是词法作用域

2.our:声明一个词法作用域,但却引用一个全局变量。换句话说,our可以在给定作用域范围内操作全局变量,退出作用域后仍然有效。和my接近,都是词法作用域
    2.1实际上是在词法作用域内定义一个和全局变量同名的词法变量,这个词法变量指向(引用)全局变量,所以修改这个词法变量的同时是在修改全局变量,退出作用域的时候,这个词法变量消失,但全局变量已经被修改了

3.local:临时操作全局变量,给全局变量赋值,退出作用域后消失,并恢复原始的全局变量值。local实现的是动态作用域
    3.1除了local这个词语的意思和局部有关,在实际效果中和局部没任何关系。如果非要理解成局部的意思,可以认为local将全局变量暂时改变成了局部变量,在退出局部环境后又恢复全局变量

4.如果要修饰除了标量、数组、hash外的其它内容,只能使用local,例如修饰文件句柄(local FH),修饰typeglob

5.当使用这3种修饰符时,如果只是声明没有赋值,my和local会将对象初始化为undef或空列表(),而our不会修改与之关联的全局变量的值(因为它操作的就是全局变量)


my和our的异同:
同:都声明一个词法作用域,退出作用域时词法变量都消失
同:都覆盖所有已同名的命令
异:my声明的词法变量存放在临时范围暂存器中,和包独立
异:our声明的词法变量,但操作的是全局变量(包变量)

our和local的异同:
同:都操作全局变量
异:our修改的全局变量在退出作用域后仍然有效
异:local修改的全局变量在退出作用域后就恢复为之前的全局变量

另一种理解my/local/our的区别:
our confines names to a scope
local confines values to a scope
my confines both names and values to a scope

访问和修改包变量

要访问某个包中的变量,可以使用完全限定名称$模块::变量。但可以在包中使用our语句修饰一个变量,使得这个变量可以直接作为包变量覆盖词法变量,直到退出作用域为止。例如Data::Dumper模块提供了控制模块行为的包变量:
use Data::Dumper;
# sort hash keys alphabetically
local $Data::Dumper::Sortkeys = 1;
# tighten up indentation
local $Data::Dumper::Indent = 1;
print Dumper(\%hash);

如果想要将包变量被别的包访问,可以让别的包通过完全限定名称的形式。但这不是一个好主意,稍后会解释。不过现在可以访问这些包变量:
package My::Number::Utilities;
use strict;
use warnings;
our $VERSION = 0.01;

$My::Number::Utilities::PI = 3.14159265359;
$My::Number::Utilities::E = 2.71828182846;
$My::Number::Uitlities::PHI = 1.61803398874; # golden ratio
@My::Number::Utilities::FIRST_PRIMES = qw(
    2 3 5 7 11 13 17 19 23 29
    31 37 41 43 47 53 59 61 67 71
);
sub is_prime {
#
}
1;

如上所见,定义了几个包变量,但这里隐藏了一个问题:$My::Number::Uitlities::PHI这个包变量的包名称拼错了。为了避免写全包名容易出错,于是使用our修饰词声明变量同时忽略包名:
our $PI = 3.14159265359;
our $E = 2.71828182846;
our $PHI = 1.61803398874; # golden ratio
our @FIRST_PRIMES = qw(
    2 3 5 7 11 13 17 19 23 29
    31 37 41 43 47 53 59 61 67 71
);

这时在其它包中也能通过完全限定名称访问该包中的变量。

但必须注意的是,直接定义包变量是能直接被其它可访问它的包修改的。例如在list_primers.pl文件中从两个包访问My::Number::Utilities包中的our $VERSION=0.01:

use strict;
use warnings;
use diagnostics;
use 5.010;

use lib 'lib';
{
    use My::Number::Utilities;
    say "block1,1: ",$My::Number::Utilities::VERSION;  # 输出:0.01
    $My::Number::Utilities::VERSION =3;
    say "block1,2: ",$My::Number::Utilities::VERSION;  # 输出:3
}

say "line: ",$My::Number::Utilities::VERSION;  # 输出:3

{
    use My::Number::Utilities;
    say "block2,1: ",$My::Number::Utilities::VERSION;  # 输出:3
    $My::Number::Utilities::VERSION=4;
    say "block2,2: ",$My::Number::Utilities::VERSION;  # 输出:4
}

上面使用了两次use导入这个模块,但实际上只在编译期间导入了一次,所以每次访问和操作的对象都是同一个目标。为了不让其它包修改这种常量型的数值,可以通过子程序来定义它。例如:
sub pi {3.14};

然后这个值就成了只读的值了。在其它想要获取这个值的包中,只需执行这个函数即可:
package Universe::Roman;
use My::Number::Utilities;
my $PI = My::Number::Utilities::pi();

所以,除非必要,不要使用our定义包变量,以避免被其它包修改。