2010-01-13 54 views
17

OK,我有以下代码:为什么Perl修改变量赋值修改数组中的值?

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

print join ", ", @ar; 

和输出?

2,3,4

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

+12

FWIW 对我的作品,这种行为在perlsyn联机帮助页记载:http://perldoc.perl.org/perlsyn.html#Foreach-Loops – friedo 2010-01-13 20:12:15

+6

你可以把它像声明的迭代器。在基本上每种实现迭代器的语言中,都可以使用迭代器来修改现有值。如果你想复制语义,只需复制一份。 :) – 2010-01-13 20:45:26

回答

23

Perl有很多这些几乎奇怪的语法,它们极大地简化了常见的任务(比如遍历列表并以某种方式更改内容),但是如果您不知道它们,可能会让您感觉不舒服。

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

+0

谢谢你解释他们为什么决定这样做。 – tster 2010-01-13 20:24:40

9

$a在这种情况下是数组元素的别名。在你的代码中没有$a =,你不会修改数组。 :-)

如果我没记错的话,mapgrep等都有相同的别名行为。

22

perldoc perlsyn

如果列表的任何元素是左值,你可以通过修改环路内VAR修改。相反,如果LIST的任何元素不是左值,则任何修改该元素的尝试都将失败。换句话说,foreach循环索引变量是您正在循环的列表中每个项目的隐式别名。

没有什么奇怪的一个记录语言功能虽然我觉得很奇怪有多少人拒绝办理入住手续时遇到的行为,他们不明白的文档。

+4

+1“我发现很奇怪有多少人拒绝阅读任何文档”。 :-P – 2010-01-13 21:59:25

+16

即使有记录,为什么不能被认为是奇怪的。如果语言规范声明返回语句后的单个语句将被执行,但不超过1,那么该怎么办呢?这会很奇怪并且记录在案。奇怪是一个意见问题。 – tster 2010-01-14 13:14:33

3

这里最重要的区别是,在声明中for循环的初始化部分中的my变量,它似乎分享当地人和词法的一些特性(与内部照顾更多的知识,以澄清别人?)

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 
+0

回复:“我认为没有其他任何构造是可能的”。你肯定听说过Perl的座右铭TIMTOWTDI(http://en.wikipedia.org/wiki/TIMTOWTDI)。获取数组元素的引用:'my $ g5r = \ $ global [5]; $ {$ G5R} ++;'。用一个全球化的别名:'我们的$ g5; * g5 = \ $ global [5]; $ G5 ++;'。本地化并使用sub:'sub doSubOnRef(&\ $){local $ _; * _ = $ _ [1]; $ _ [0] - >()} doSubOnRef {$ _ ++} $ global [5];'。检查它们是否都指向相同的东西:'my @equality = map {$ _ == \ $ global [5]} \ $ global [5],\ $ g5,$ g5r,doSubOnRef {\ $ _} $ global [5];' – 2010-01-14 05:44:53

+0

@Chris =>不同之处在于,在我的例子中,'$ subs [5]'是别名周围的闭包,这也恰好是(但不一定是)全局的。我的观点是,我不知道有任何其他方式围绕别名创建闭包(仅使用核心)。在你的例子中,没有办法从doSubOnRef返回一个闭包,因为typeglob别名/本地只适用于包变量,它不能(除了上述情况)关闭 – 2010-01-14 06:30:59

+0

它不是轻量级的,但它可以完成与绑定词汇:@subs_相当于您的@subs:'package TiedScalarRef;需要Tie :: Scalar;我们的@ISA = qw(Tie :: StdScalar);子FETCH {$ {$ _ [0] - > SUPER :: FETCH(@_)}}子存储{$ {$ _ [0] - > SUPER :: FETCH($ _ [0])} = $ _ [ 1]}'和'package main;我的@subs_;我的$ i(0..9){my $ r; tie $ r,'TiedScalarRef',\ $ global [$ i]; push @subs_,sub {++ $ r}} $ subs_ [5]();' – 2010-01-14 09:18:22

4

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

我的理解是,@_formapgrep混叠行为提供了速度和内存优化以及提供创意有趣的可能性。发生的事情本质上是构造块的传递引用调用。通过避免不必要的数据复制,节省了时间和内存。

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::MoreUtilsapply功能。它的工作原理与map类似,但是会创建主题变量的副本,而不是使用引用。如果你讨厌写作类似的代码:

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

你一定会喜欢apply,这使得它分为:

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

事情需要注意的:如果你通过只读值到这些结构的一个修改它的输入值,你会得到一个“尝试修改一个只读值”的错误。

perl -e '$_++ for "o"' 
0

您的$ 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; 
    } 
} 
0

尝试

foreach my $a (@_ = @ar) 

now mod如果$ a不会修改@ar。在v5.20.2