Область действия переменной по умолчанию $ _ в Perl - PullRequest
2 голосов
/ 20 сентября 2011

У меня есть следующий метод, который принимает переменную, а затем отображает информацию из базы данных:

sub showResult {
    if (@_ == 2) {
        my @results = dbGetResults($_[0]);
        if (@results) {
            foreach (@results) {
                print "$count - $_[1] (ID: $_[0])\n";
            }
        } else {
            print "\n\nNo results found";
        }
   }
}

Все работает нормально, кроме строки печати в цикле foreach. Эта переменная $ _ по-прежнему содержит значения, переданные методу.

В любом случае можно ли принудительно задать новую область значений в $ _ или она всегда будет содержать исходные значения?

Если есть какие-нибудь хорошие учебники, которые объясняют, как работает область действия $ _, это тоже было бы круто!

Спасибо

Ответы [ 3 ]

9 голосов
/ 20 сентября 2011

Проблема здесь в том, что вы используете @_ вместо $_. Цикл foreach меняет $_, скалярную переменную, а не @_, к которой вы обращаетесь, если индексируете ее с помощью $_[X]. Кроме того, проверьте еще раз код, чтобы увидеть, что внутри @results. Если это массив массивов или ссылок, вам может понадобиться использовать косвенный ${$_}[0] или что-то в этом роде.

4 голосов
/ 20 сентября 2011

В Perl имя _ может относиться к ряду различных переменных:

Наиболее распространенными являются:

$_ the default scalar (set by foreach, map, grep)
@_ the default array  (set by calling a subroutine)

Менее распространенные:

%_ the default hash (not used by anything by default)
 _ the default file handle (used by file test operators)
&_ an unused subroutine name
*_ the glob containing all of the above names

Каждая из этих переменных может использоваться независимо от других.Фактически, единственный способ, которым они связаны, заключается в том, что все они содержатся внутри глобуса *_.

Поскольку сигилы меняются в зависимости от массивов и хэшей, при доступе к элементу вы используете символы скобок для определенияк какой переменной вы обращаетесь:

$_[0]   # element of @_
$_{...} # element of %_

$$_[0]  # first element of the array reference stored in $_
$_->[0] # same

Цикл for / foreach может принимать имя переменной для использования вместо $_, и это может быть понятнее в вашей ситуации:

for my $result (@results) {...}

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


Поскольку ваш вопрос больше связан сИмена переменных, кроме области, я не обсуждал фактическую область вокруг цикла foreach, но в целом следующий код эквивалентен тому, что у вас есть.

for (my $i = 0; $i < $#results; $i++) {
    local *_ = \$results[$i];
    ...
}

Строка local *_ = \$results[$i] устанавливает $iй элемент @results в скалярную щель шара *_, он же $_.На данный момент $_ содержит псевдоним элемента массива.Локализация будет разматываться в конце цикла.local создает динамическую область видимости, поэтому любые подпрограммы, вызываемые из цикла, увидят новое значение $_, если только они не локализуют его.Об этих понятиях доступно гораздо больше деталей, но я думаю, что они выходят за рамки вашего вопроса.

2 голосов
/ 21 сентября 2011

Как уже отмечали другие:

  • Вы действительно используете @_, а не $_ в своем заявлении о печати. ​​
  • Нехорошо хранить вещи в этихпеременные, поскольку они используются в других местах.

Официально, $_ и @_ являются глобальными переменными и не являются членами какого-либо пакета.Вы можете локализовать область действия с помощью my $_, хотя это, вероятно, очень, очень плохая идея.Проблема в том, что Perl может использовать их, даже не зная об этом.Это плохая практика - полагаться на их значения более чем на несколько строк.

Вот небольшая перезапись в вашей программе, которая максимально избавляется от зависимости от @_ и $_:

sub showResults {
    my $foo = shift;    #Or some meaningful name
    my $bar = shift;    #Or some meaningful name

    if (not defined $foo) {
       print "didn't pass two parameters\n";
       return;  #No need to hang around
    }
    if (my @results = dbGetResults($foo)) {
        foreach my $item (@results) {
        ...
    }
}

Некоторые модификации:

  • Я использовал shift, чтобы дать вашим двум параметрам фактические имена.foo и bar не являются хорошими именами, но я не мог выяснить, откуда взято dbGetResults, поэтому я не мог понять, какие параметры вы искали.@_ все еще используется при передаче параметров, и мой shift зависит от значения @_, но после первых двух строк я свободен.
  • Так как ваши двапараметры имеют фактические имена, я могу использовать if (not defined $bar), чтобы проверить, были ли переданы оба параметра.Я также изменил это на негатив.Таким образом, если они не передали оба параметра, вы можете выйти рано.Таким образом, ваш код имеет один отступ, а у вас нет структуры if, которая занимает всю подпрограмму.Это облегчает понимание вашего кода.
  • Я использовал foreach my $item (@results) вместо foreach (@results) и зависит от $_.Опять же, становится понятнее, что делает ваша программа, и вы бы не спутали $_->[0] с $_[0] (я думаю, это то, что вы делали)Было бы очевидно, что вы хотели $item->[0].
...