深入理解Perl变量作用域

发布时间:2020-05-05编辑:脚本学堂
深入理解Perl变量作用域

2.local有何好处?

因为local实际不创建本地变量,它并非很有用。在上述示例里,假如B碰巧修改了$lo的值,这样A设置的值就被覆盖掉。这点我们确实不想它发生。我们希望每个函数有它自己的变量,它们不会被其他函数触及到。这就是my所能做到的。

为 什么会有local呢?答案90%是因为历史原因。早期的perl版本仅有全局变量。local非常容易执行,它作为对本地变量问题的局部解决方案而增加 到perl4里。后来在perl5里做了更多工作,真正的本地变量被添加到该语言里。不同于local,新的本地变量以单词my来申明。之所以选择 my,是因为它暗示着隐私,也因为它非常短;短小的单词也许会鼓励你使用它来代替local。my也比local运行更快。

何时使用my,以及何时使用local呢?

答案很简单:总使用my,绝不要使用local。

3.my变量的其他特性
每次控制抵达my申明,perl创建一个新的,初始的变量。例如,如下代码打印x=1 50次:
 

复制代码 代码如下:
for (1 .. 50) {
    my $x;
    $x++;
    print "x=$xn";
  }

每次遍历循环时,你得到一个新的$x,其值初始化为undef。

假如申明在循环之外,控制只会通过它一次,所以这里就只有一个变量:
 

复制代码 代码如下:
{ my $x;
    for (1 .. 50) {
$x++;
print "x=$xn";
    }    
  }
 

这会打印x=1, x=2, x=3, ... x=50.

可 以利用这点来玩个游戏。假设有个函数,它需要从一个调用到下一个调用中记住某个值。例如,考虑一个随机数产生器。典型的随机数产生器(类似perl的 rand函数)有个种子在其中。种子就是一个数。当请求随机数产生器来获取随机数时,该函数基于种子来执行某些运算,然后返回结果。它也会存储该结果,并 将其作为下一次函数调用的种子。

如下是典型代码:
 

复制代码 代码如下:
  $seed = 1;
  sub my_rand {
    $seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
    return $seed;
  }

典型的输出:
  16838
  14666
  10953
  11665
  7451
  26316
  27974
  27550

这里有个问题,因为$seed是个全局变量,那意味着我们必须担忧某个人可能无意中修改它。或者别人有意破坏它,这就影响了程序的结果。假如该函数用于赌博程序中,并且别人破坏了它的随机数产生器,你想想会发生什么?

但是我们不能在函数里申明$seed为my变量:
 

复制代码 代码如下:
sub my_rand {
    my $seed;
    $seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
    return $seed;
  }

假如这样做了,当每次调用my_rand时,$seed会被初始化为undef。我们实际需要的是,在每次调用my_rand时,$seed会保留其值。

如下是解决方法:
 

复制代码 代码如下:
{ my $seed = 1;
    sub my_rand {
$seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
return $seed;
    }
  }

申明在函数之外,所以它仅仅在程序编译时执行一次,而不是每次函数调用时都执行。但它是个my变量,并且其位于代码块里,所以它仅对块里的代码可见。my_rand是块里的唯一其他东西,所以$seed仅可被my_rand函数访问。

4.关于my变量的补充

1)不能对以标点符号命名的变量使用my申明,例如_, @_, 或$$。也不能对后台引用的变量$1, $2, ... 使用my申明。my的作者认为那会把事情搞糟。
2)明显的,不能申明my $DBI::errstr,因为那会有冲突:它认为包变量$DBI::errstr是个词法变量。但是可以申明local $DBI::errstr;它存储local $DBI::errstr的当前值,并在代码块结束处恢复它。
3)在perl 5.004及更高版本里,可以这样写:
  foreach my $i (@list) {
它限制$i在循环范围内。类似的,
  for (my $i=0; $i<100; $i++) {
限制了$i在for循环里。

(三)变量申明
假如你在编写某个函数,并且你想要它有私有变量,就必须使用my来申明变量。假如忘记了,会发生什么事?
  sub function {
    $x = 42;  # Oops, should have been my $x = 42.
  }

在该情形下,你的函数修改了全局包变量$x。假如你在其他地方要用到那个包变量,那对程序将是灾难。
最近版本的perl有针对这点的保护选项,你可以激活它。假如放置:
   use strict 'vars';
在程序的顶部,perl将要求包变量有明确的包限定词。$x=42里的$x没有这样的限定词,所以程序甚至不会通过编译;代替的,编译器会异常中断,并输出如下错误消息:
Global symbol "$x" requires explicit package name at ...
假如你希望$x是个私有my变量,你可以回头增加my。假如你确实想使用全局包变量,你能回头改变它为:
$main::x = 42;
或其他相应的包。

use strict还有其他的检测选项,请看perldoc strict的更多细节。

现在假设你在编写Algorithms::KnuthBendix模块,你想使用strict vars保护模式,但假如任何时候你需要一遍又一遍的敲入$Algorithms::KnuthBendix::Error,你会觉得很烦。

你可以告诉strict vars生成一个例外:
 

复制代码 代码如下:
     package Algorithms::KnuthBendix;
     use vars '$Error';

这样就在你使用短名字$Error时,避免了包变量$Algorithms::KnuthBendix::Error导致的strict vars失败。

如下写法,也可以在某个代码块里关闭strict vars:
 

复制代码 代码如下:
 { no strict 'vars';
 # strict vars is off for the rest of the block.
 }

(四)总结
包变量总是全局的。它们有一个名字和一个包限定词。可以忽略包限定词,这样perl会使用默认的包,默认包可由package申明设定。对私有变量,请使用my。不要使用local,它已过时。
避免使用全局变量,因为它难以确保程序的2部分不会错误的使用对方的变量。
为了避免意外使用全局变量,请在程序里使用use strict 'vars'。它会检查并确保所有的变量要么是申明为私有的,要么明确使用了包限定词,要么明确使用use vars来申明。

(五)关于'our'的补充

perl 5.6.0介绍了一个新的our(...)申明。它的语法与my()相同,它是use vars的代替品。

如果不深究细节,our()就类似于use vars;它的唯一影响是申明变量,以便它们免除strict 'vars'的检查。然而相对于use vars,它可能有2个优势:语法不那么怪异,影响是词法作用域。也就是说,它让stict检查失效的范围仅仅在当前块之内:
 

复制代码 代码如下:
   use strict 'vars';
   {
our($x);
$x = 1;   # 这里使用全局变量$x没问题
   }
   $x = 2;     # 这里使用$x通常引起编译时错误

所以使用use vars '$x'申明时,可以在任何地方使用全局变量$x。our($x)仅仅允许在程序的某些块里申明全局变量$x,假如意外的在其他地方使用它,仍会导致错误。