2

我有以下方法,它接受一个变量,然后显示来自数据库的信息:

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 循环中的打印行。这个 $_ 变量仍然包含传递给方法的值。

无论如何要在 $_ 上“强制”新的值范围,还是它总是包含原始值?

如果有任何好的教程来解释 $_ 的范围是如何工作的,那也很酷!

谢谢

4

3 回答 3

9

这里的问题是你使用真的@_而不是$_. foreach循环改变了标$_量变量 not @_,如果您使用$_[X]. 另外,再次检查代码以查看其内部内容@results。如果它是数组或引用的数组,您可能需要使用间接${$_}[0]或类似的东西。

于 2011-09-20T19:11:08.463 回答
4

在 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

这些变量中的每一个都可以独立于其他变量使用。事实上,它们相关的唯一方式是它们都包含在*_glob 中。

由于 sigils 随数组和散列而变化,因此在访问元素时,您可以使用括号字符来确定您正在访问的变量:

$_[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];
    ...
}

该行将第 th 个元素local *_ = \$results[$i]安装到glob的标量槽中,即. 此时包含数组元素的别名。本地化将在循环结束时展开。 创建一个动态范围,因此从循环内调用的任何子例程都将看到新值 of ,除非它们也将其本地化。有关这些概念的更多详细信息,但我认为它们超出了您的问题范围。$i@results*_$_$_local$_

于 2011-09-20T19:58:37.347 回答
2

正如其他人指出的那样:

  • 你真的在使用@_而不是$_在你的打印语句中。
  • 将东西保存在这些变量中是不好的,因为它们在其他地方使用。

正式地,$_并且@_是全局变量,并且不是任何包的成员。您可以使用 本地化范围,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是从哪里来的,所以我不知道你在找什么参数。@_传递参数时仍在使用,而 my取决于shift的值@_,但在前两行之后,我是自由的。
  • 由于您的两个参数具有实际名称,因此我可以使用if (not defined $bar)来查看两个参数是否都已通过。我也把它改成了否定的。这样,如果他们没有传递两个参数,您可以提前退出。这样一来,您的代码就少了一个缩进,并且您没有占用整个子例程的 if 结构。它使您更容易理解您的代码。
  • 我使用foreach my $item (@results)而不是foreach (@results)依赖于$_. 同样,你的程序在做什么更清楚,你不会混淆$_->[0]$_[0]我认为这就是你在做什么)。你想要的很明显$item->[0]
于 2011-09-20T20:34:47.473 回答