代码之家  ›  专栏  ›  技术社区  ›  tster

为什么PerlForEach变量赋值会修改数组中的值?

  •  17
  • tster  · 技术社区  · 14 年前

    好的,我有以下代码:

    use strict;
    my @ar = (1, 2, 3);
    foreach my $a (@ar)
    {
      $a = $a + 1;
    }
    
    print join ", ", @ar;
    

    输出?

    2, 3, 4

    搞什么鬼?为什么会这样?这会一直发生吗?$A真的不是局部变量吗?他们在想什么?

    7 回复  |  直到 9 年前
        1
  •  24
  •   Anon.    14 年前

    Perl有许多这些几乎很奇怪的语法功能,它们大大简化了常见的任务(比如遍历列表并以某种方式更改内容),但是如果您不知道它们,可能会使您陷入困境。

    $a 是数组中的值的别名-这允许您修改循环中的数组。如果您不想这样做,请不要修改 $A .

        2
  •  22
  •   Sinan Ünür    14 年前

    perldoc perlsyn :

    如果列表中的任何元素是左值,则可以通过修改循环中的var来修改它。相反,如果列表中的任何元素不是左值,则任何修改该元素的尝试都将失败。换句话说,for each循环索引变量是要循环遍历的列表中每个项的隐式别名。

    什么都没有 奇怪的 古怪的 关于一 文件化的 语言特性尽管我发现有多少人在遇到他们不理解的行为时拒绝检查文档是很奇怪的。

        3
  •  9
  •   C. K. Young    14 年前

    $a 在本例中,是数组元素的别名。只是没有 $a = 在代码中,不会修改数组。-)

    如果我记错了, map , grep 等等,都有相同的混叠行为。

        4
  •  4
  •   daotoad    14 年前

    正如其他人所说,这是有记录的。

    我的理解是 @_ , for , map grep 提供一个速度和内存优化,以及提供有趣的可能性的创意。实际发生的是,构造块的传递引用调用。这样可以避免不必要的数据复制,从而节省时间和内存。

    use strict;
    use warnings;
    
    use List::MoreUtils qw(apply);
    
    my @array = qw( cat dog horse kanagaroo );
    
    foo(@array);
    
    
    print join "\n", '', 'foo()', @array;
    
    my @mapped = map { s/oo/ee/g } @array;
    
    print join "\n", '', 'map-array', @array;
    print join "\n", '', 'map-mapped', @mapped;
    
    my @applied = apply { s/fee//g } @array;
    
    print join "\n", '', 'apply-array', @array;
    print join "\n", '', 'apply-applied', @applied;
    
    
    sub foo {
       $_ .= 'foo' for @_;
    }
    

    注意使用 List::MoreUtils apply 功能。它工作得很像 地图 但是复制主题变量,而不是使用引用。如果你讨厌写这样的代码:

     my @foo = map { my $f = $_; $f =~ s/foo/bar/ } @bar;
    

    你会爱上的 应用 使其成为:

     my @foo = apply { s/foo/bar/ } @bar;
    

    注意:如果将只读值传递到修改其输入值的其中一个构造中,将出现“尝试修改只读值”错误。

    perl -e '$_++ for "o"'
    
        5
  •  3
  •   Eric Strom    14 年前

    这里的重要区别在于,当您声明 my 变量的初始化部分 for 循环,它似乎共享了局部变量和词汇变量的一些属性(有人对内部知识有更多的了解需要澄清?)

    my @src = 1 .. 10;
    
    for my $x (@src) {
        # $x is an alias to elements of @src
    }
    
    for (@src) {
        my $x = $_;
        # $_ is an alias but $x is not an alias
    }
    

    有趣的副作用是,在第一种情况下, sub{} 在for循环中定义的是一个围绕列表中任何元素的闭包。 $x 化名为。知道了这一点,就有可能(尽管有点奇怪)关闭一个别名值,它甚至可能是一个全局值,我认为任何其他构造都不可能。

    our @global = 1 .. 10;
    my @subs;
    for my $x (@global) { 
        push @subs, sub {++$x}
    }
    
    $subs[5](); # modifies the @global array
    
        6
  •  0
  •   sorpigal    14 年前

    您的$A只是在循环遍历列表中的每个元素时用作别名。它被用来代替美元。可以看出$A不是局部变量,因为它是在块外部声明的。

    更明显的是,如果你把分配给$A看作是一个$的代言人,那么它为什么会改变列表的内容。事实上,如果您这样定义自己的迭代器,$不存在。

    foreach my $a (1..10)
        print $_; # error
    }
    

    如果你想知道问题的关键是什么,考虑一下这个例子:

    my @row = (1..10);
    my @col = (1..10);
    
    foreach (@row){
        print $_;
        foreach(@col){
            print $_;
        }
    }
    

    在这种情况下,为$_

    foreach my $x (@row){
        print $x;
        foreach my $y (@col){
            print $y;
        }
    }
    
        7
  •  0
  •   Matt    9 年前

    尝试

    foreach my $a (@_ = @ar)
    

    现在修改$A不会修改@ar。 在v5.20.2上为我工作