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

ajw*_*ood 2 variables perl initialization declaration

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

my $foo = 'bar' if 0;

my $baz;
$baz = 'qux' if 0;
Run Code Online (Sandbox Code Playgroud)

当它们出现在循环的顶部时,差异很大.例如:

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";
}
Run Code Online (Sandbox Code Playgroud)

结果是

undefined
defined
defined
defined
==
undefined
defined
undefined
defined
Run Code Online (Sandbox Code Playgroud)

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

ike*_*ami 12

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


my $x 有三个记录的效果:

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

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

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

for (1..100) {
   my $x = rand();
   print "$x\n";
}
Run Code Online (Sandbox Code Playgroud)

这意味着每个循环迭代只需要两次或三次内存分配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
Run Code Online (Sandbox Code Playgroud)

但这样做需要忽略禁止它的文档.以上可以安全地使用

{
   my $state = 5;
   sub foo {
      print "$state\n";
      ++$state;
   }
}
Run Code Online (Sandbox Code Playgroud)

要么

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

sub foo {
   state $state = 5;
   print "$state\n";
   ++$state;
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. "变量"可能意味着一些事情.我不确定这里的定义是否准确,但无关紧要.

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

    my @a;
    for (...) {
        my $x = ...;
        push @a, \$x;
    }
    
    Run Code Online (Sandbox Code Playgroud)