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

时间:2013-06-28 02:10:09

标签: perl variables initialization declaration

以下两个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 ? "defined\n" : "undefined\n";
    $foo = 'bar';
    print defined $foo ? "defined\n" : "undefined\n";
}

print "==\n";

foreach my $m (0,1){
    my $baz;
    $baz = 'qux' if 0;
    print defined $baz ? "defined\n" : "undefined\n";
    $baz = 'qux';
    print defined $baz ? "defined\n" : "undefined\n";
}

结果

undefined
defined
defined
defined
==
undefined
defined
undefined
defined

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

2 个答案:

答案 0 :(得分:12)

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


my $x有三种记录效果:

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

简而言之,它假设像Java Scalar x = new Scalar();,除非它在表达式中使用时返回变量。

但如果它实际上以这种方式工作,则以下将创建100个变量:

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

这意味着仅my每次循环迭代就有两次或三次内存分配!非常昂贵的前景。相反,Perl只创建一个变量并在范围的末尾清除它。所以实际上,my $x实际上做了以下事情:

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

因此,只创建了一个变量 [2] 。这比在每次输入范围时创建一个CPU更有效率。

现在考虑如果有条件地执行my,或者根本不执行会发生什么。通过这样做,您将阻止它放置指令以清除堆栈上的变量,因此变量永远不会丢失其值。显然,这并不意味着发生,所以这就是为什么不允许my ... if ...;


有些人利用如下实施:

sub foo {
   my $state if 0;
   $state = 5 if !defined($state);
   print "$state\n";
   ++$state;
}

foo();  # 5
foo();  # 6
foo();  # 7

但这样做需要忽略禁止它的文档。使用

可以安全地实现上述目标
{
   my $state = 5;
   sub foo {
      print "$state\n";
      ++$state;
   }
}

use feature qw( state );  # Or: use 5.010;

sub foo {
   state $state = 5;
   print "$state\n";
   ++$state;
}

注意:

  1. “变量”可能意味着一些事情。我不确定这里的定义是否准确,但没关系。

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

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

答案 1 :(得分:-1)

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

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

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