Почему Perl назначает переменную foreach, изменяя значения в массиве? - PullRequest
17 голосов
/ 13 января 2010

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

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

print join ", ", @ar;

а на выходе?

2, 3, 4

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

Ответы [ 7 ]

24 голосов
/ 13 января 2010

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

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

22 голосов
/ 13 января 2010

См. perldoc perlsyn :

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

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

9 голосов
/ 13 января 2010

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

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

4 голосов
/ 14 января 2010

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

Насколько я понимаю, поведение псевдонимов @_, 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"'
3 голосов
/ 14 января 2010

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

Попробуйте

foreach my $a (@_ = @ar)

теперь изменение $ a не изменяет @ar. У меня работает на v5.20.2

0 голосов
/ 14 января 2010

Ваш $ 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...