Perl 变量作用域

Perl variables scoping

我想用 this 方法从数组中删除重复值。重复删除必须在循环内执行。 这是一个演示我遇到的问题的最小示例:

use strict;

for (0..1){
    my %seen;
    sub testUnique{
        return !$seen{shift}++;
    }

    my $res = testUnique(1);

    if($res){
        print "unique\n";
    }else{
        print "non-unique\n";
    }
}

我在循环中定义了 %seen 散列,因此我希望它仅在循环的单次迭代期间定义。 然而,上面代码的结果是:

unique
non-unique

通过一些调试打印,我发现 %seen 的值从一个迭代到另一个迭代被保留。

我尝试了一个简单的

for (0..1){
    my %seen;
    $seen{1}++;
    print "$seen{1}\n";
}

这一个按预期工作。它打印:

1
1

所以,我猜问题出在内部函数 testUnique 上。 有人能给我解释一下这是怎么回事吗?

您的 testUnique 子关闭超过 %seen 的第一个实例。即使它在 for 循环内,子例程也不会重复编译。

您的代码被编译一次,包括 for 循环 的顶部初始化词法范围变量 %hash 的部分。 =16=]

以下将产生您想要的输出,但我不确定我看到的是沿着这条路走下去:

#!/usr/bin/env perl

use warnings;
use strict;

for (0..1){
    my %seen;
    my $tester = sub {
        return !$seen{shift}++;
    };

    print $tester->(1) ? "unique\n" : "not unique\n";
}

子例程只能定义一次,不会为循环的每次迭代重新创建。因此,它只包含对初始 %seen 哈希的引用。添加一些输出有助于澄清这一点:

use strict;
use warnings;

for(0 .. 1) {
    my %seen = ();
    print "Just created " . \%seen . "\n";

    sub testUnique {
        print "Testing " . \%seen . "\n";
        return ! $seen{shift} ++;
    }

    if(testUnique(1)) {
        print "unique\n";
    }
    else {
        print "non-unique\n";
    }
}

输出:

Just created HASH(0x994fc18)
Testing HASH(0x994fc18)
unique
Just created HASH(0x993f048)
Testing HASH(0x994fc18)
non-unique

这里可以看出,初始哈希是唯一被测试的。

欢迎来到闭包的世界。

sub make_closure {
    my $counter = 0;
    return sub { return ++$counter };
}

my $counter1 = make_closure();
my $counter2 = make_closure();

say $counter1->();  # 1
say $counter1->();  # 2
say $counter1->();  # 3
say $counter2->();  # 1
say $counter2->();  # 2
say $counter1->();  # 4

sub { } 捕获作用域内的词法变量,即使它们所在的作用域消失了,也可以让 sub 访问它们。

你每天都在不知不觉中使用这个能力。

 my $foo = ...;
 sub print_foo { print "$foo\n"; }

如果 subs 没有捕获,上面的代码将不会在模块中工作,因为在调用模块中的任何函数之前通常会退出文件的词法范围。

不仅 print_foo 需要捕获 $foo 才能使上述工作正常,而且在编译时也必须这样做。

sub testUnique {
    return !$seen{shift}++;
}

基本相同

BEGIN {
    *testUnique = sub {
        return !$seen{shift}++;
    };
}

这意味着 sub { } 在编译时执行,这意味着它捕获 %seen 在编译时 存在 ,这意味着在循环之前开始了。

循环的第一遍将使用相同的 %seen,但是将为每个后续遍创建一个新的 %seen 以允许

my @outer;
for (...) {
   my @inner = ...;
   push @outer, \@inner;
}

如果您在 运行 时执行 sub { },就没有问题。

for (0..1){
    my %seen;
    local *testUnique = sub {
        return !$seen{shift}++;
    };

    my $res = testUnique(1);

    if($res){
        print "unique\n";
    }else{
        print "non-unique\n";
    }
}