Perl变量范围

时间:2015-02-16 21:18:12

标签: perl scope

我希望使用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。 有人可以解释一下这里发生了什么吗?

3 个答案:

答案 0 :(得分:4)

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

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

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

#!/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";
}

答案 1 :(得分:3)

子程序只能定义一次,并且不会为循环的每次迭代重新创建。因此,它只保留对初始%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

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

答案 2 :(得分:2)

欢迎来到封闭世界。

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 { }捕获范围内的词汇变量,即使它们存在的范围消失,也可以对它们进行子访问。

你每天都在不知情的情况下使用这种能力。

 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";
    }
}