Подтвердить что ты не робот

Почему переменная Perl foreach изменяет значения в массиве?

ОК, у меня есть следующий код:

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

print join ", ", @ar;

и выход?

2, 3, 4

Что, черт возьми? Почему это так? Это всегда произойдет? $$ не является локальной переменной? Что, где они думают?

4b9b3361

Ответ 1

В Perl есть много этих почти нечетных синтаксических вещей, которые значительно упрощают общие задачи (например, перебирают список и каким-то образом меняют содержимое), но могут вас трогать, если вы не знаете о них.

$a псевдоним для значения в массиве - это позволяет вам изменять массив внутри цикла. Если вы не хотите этого делать, не изменяйте $a.

Ответ 2

См. perldoc perlsyn:

Если какой-либо элемент LIST является lvalue, вы можете изменить его, изменив VAR внутри цикла. И наоборот, если какой-либо элемент LIST не является значением lval, любая попытка изменить этот элемент завершится неудачно. Другими словами, переменная индекса цикла foreach является неявным псевдонимом для каждого элемента списка, который вы зацикливаете.

Нет ничего странного или нечетного о языковой функции документально, хотя мне кажется странным, сколько людей отказывается проверять документы, если они не знакомы с поведением, которое они не понимают.

Ответ 3

$a в этом случае является псевдонимом элемента массива. Просто не используйте $a = в своем коде, и вы не будете изменять массив.: -)

Если я правильно помню, map, grep и т.д. все имеют одно и то же поведение псевдонимов.

Ответ 4

Как говорили другие, это документировано.

Я понимаю, что поведение псевдонимов @_, 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. Он работает как map, но создает копию переменной темы, а не использует ссылку. Если вы ненавидите писать код, например:

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

вам понравится apply, что делает его следующим:

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

Что-то, на что нужно обратить внимание: если вы передадите значения только для чтения в одну из этих конструкций, которая изменяет свои входные значения, вы получите сообщение об ошибке "Модификация ошибки, доступной только для чтения".

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

Ответ 5

Важным отличием здесь является то, что когда вы объявляете переменную 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
}

интересным побочным эффектом этого является то, что в первом случае a sub{}, определенный в цикле for, является замыканием вокруг любого элемента списка $x, который был псевдонимом. зная это, можно (хотя и немного странно) закрыть вокруг сглаженного значения, которое может быть даже глобальным, что, по моему мнению, невозможно с любой другой конструкцией.

our @global = 1 .. 10;
my @subs;
for my $x (@global) { 
    push @subs, sub {++$x}
}

$subs[5](); # modifies the @global array

Ответ 6

Ваш $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

Try

foreach my $a (@_ = @ar)

теперь модификация $a не изменяет @ar. Работает для меня на v5.20.2