为什么这个Perl变量保持其值



以下两个Perl变量声明之间有什么区别?

my $foo = 'bar' if 0;
my $baz;
$baz = 'qux' if 0;

当这些出现在循环的顶部时,差异是显著的。例如:

use warnings;
use strict;
foreach my $n (0,1){
    my $foo = 'bar' if 0;
    print defined $foo ? "definedn" : "undefinedn";
    $foo = 'bar';
    print defined $foo ? "definedn" : "undefinedn";
}
print "==n";
foreach my $m (0,1){
    my $baz;
    $baz = 'qux' if 0;
    print defined $baz ? "definedn" : "undefinedn";
    $baz = 'qux';
    print defined $baz ? "definedn" : "undefinedn";
}

中的结果

undefined
defined
defined
defined
==
undefined
defined
undefined
defined

似乎if 0失败了,所以foo永远不会重新初始化为undef。在这种情况下,它首先是如何声明的?

首先,请注意,my $foo = 'bar' if 0;被记录为未定义的行为,这意味着它可以做任何事情,包括崩溃。但我会解释到底会发生什么。


my $x有三个记录在案的效应:

  • 它在编译时声明一个符号
  • 它在执行时创建一个新变量
  • 它在执行时返回新变量

简而言之,假设它与Java的Scalar x = new Scalar();类似,只是如果在表达式中使用,它会返回变量。

但如果它真的是这样工作的话,下面将创建100个变量:

for (1..100) {
   my $x = rand();
   print "$xn";
}

这意味着对于单独的my,每个循环迭代将分配两到三个内存!一个非常昂贵的前景。相反,Perl只创建一个变量,并在作用域结束时将其清除。所以在现实中,my $x实际上做了以下事情:

  • 它在编译时声明一个符号
  • 它在编译时创建变量[1]
  • 它在堆栈上放置一个指令,当作用域退出时,该指令将清除变量[2]
  • 它在执行时返回新变量

因此,只有一个变量被创建[2]。这比每次输入作用域时创建一个作用域的CPU效率高得多。

现在考虑一下,如果有条件地执行my,或者根本不执行,会发生什么。这样做可以防止它在堆栈上放置清除变量的指令,这样变量就永远不会丢失其值。显然,这是不应该发生的,所以my ... if ...;是不被允许的。


一些人利用了以下实现:

sub foo {
   my $state if 0;
   $state = 5 if !defined($state);
   print "$staten";
   ++$state;
}
foo();  # 5
foo();  # 6
foo();  # 7

但这样做需要忽略禁止它的文件。以上可以使用安全地实现

{
   my $state = 5;
   sub foo {
      print "$staten";
      ++$state;
   }
}

use feature qw( state );  # Or: use 5.010;
sub foo {
   state $state = 5;
   print "$staten";
   ++$state;
}

注:

  1. "变量"可以指几件事。我不确定这里哪个定义是准确的,但没关系。

  2. 如果除了子本身之外的任何内容都包含对变量的引用(REFCNT>1),或者如果变量包含对象,则指令将用新变量替换该变量(在作用域出口上),而不是清除现有变量。这允许以下功能正常工作:

    my @a;
    for (...) {
        my $x = ...;
        push @a, $x;
    }
    

请参阅ikegami的更好答案,可能在上面。

在第一个例子中,由于条件的原因,您从未在循环中定义$foo,因此当您使用它时,您将引用一个隐式声明的全局变量,然后将其赋值。然后,第二次通过循环,外部变量已经定义。

在第二个示例中,每次执行块时都在块内部定义$baz。因此,第二次循环时,它是一个新的、尚未定义的局部变量。

最新更新