【问题标题】:Scope of the default variable $_ in PerlPerl 中默认变量 $_ 的作用域
【发布时间】:2011-09-20 19:07:29
【问题描述】:

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

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

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

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

谢谢

【问题讨论】:

  • 一般经验法则是除非绝对必要,否则永远不要使用默认标量。与foreach (@results) 相比,foreach my $result (@results) 的代码可维护性/可读性要好得多。仅仅因为 Perl 在输入字符与可读性的权衡方面非常宽容,并不意味着你不应该总是在可读性方面犯错。总是像下一个维护你的代码的开发者是一个知道你住在哪里的狂暴的精神病一样编码:)
  • 其实这个问题与$_的范围无关,是对Perl命名约定的误解。

标签: perl default perlvar perlsyn


【解决方案1】:

这里的问题是您实际上使用的是@_ 而不是$_foreach 循环更改$_,标量变量,而不是@_,如果您通过$_[X] 对其进行索引,您将访问它。此外,再次检查代码以查看 @results 中的内容。如果它是数组或引用的数组,您可能需要使用间接${$_}[0] 或类似的东西。

【讨论】:

  • $$_[0] 非常好。并且更容易打字。 :)
  • @ikegami,@tchrist,很好,注意到了。不过,目前还不清楚@results 中的内容。
  • @tchrist - 对不起,但我必须不同意。 $_-> 乍一看清楚地表明您正在取消引用(并且是 x-of-x-of-x 嵌套取消引用的通用符号)。 $$_[0] 更难阅读,例如您很容易将其误读为$_[0]; AND 不能作为进一步取消引用的符号(我通常的试金石测试是在凌晨 2 点半睡半醒地查看不熟悉的代码,CEO 大喊大叫你要解决昨天的生产问题;并让代码由初级开发人员维护)。代码可读性方面,$$ 语法不如箭头好。
  • @DVK => 任何时候你有背靠背的印记($$@$%$),乍一看它表明你正在取消引用。 $$_[0] 语法适用于进一步取消引用:$$_[0]{this}[0]{that}$$_[0] 语法也类似于扩展的${$_}[0] 语法和@$_[1,2] 切片语法。在教开发人员时,您只需要告诉他们,对于使用 @array 变量的任何工作语法,您可以将 array 的文本替换为 $array 并且新代码将正确使用引用。换句话说,'$array' 是完整的标识符。
  • @Eric - 你的论点是错误的。我 100% 同意你所说的——我知道从语言设计/效率的角度来看$* 双sygiling 是一件好事且合乎逻辑的事情。当即时可读性/清晰度非常重要时,不会使其更具可读性。我认为没有人做过研究,但我敢打赌,在认知受损的情况下,更多人在快速浏览$$_[0]{this}[0]{that}$arg->{this}->[0]->{that} 时会犯各种阅读错误。看看 David W. 的答案代码与 OP 的
【解决方案2】:

在 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];
    ...
}

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

【讨论】:

  • for循环测试表达式应该是$i &lt;= $#results,因为$#results是数组@results的最后一个索引。
【解决方案3】:

正如其他人指出的那样:

  • 您在打印语句中确实使用了@_ 而不是$_
  • 将内容保存在这些变量中并不好,因为它们在其他地方使用。

正式地,$_@_ 是全局变量,不是任何包的成员。您可以使用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 为您的两个参数提供实际名称。 foobar 不是好名字,但我找不到 dbGetResults 的来源,所以我无法弄清楚你在寻找什么参数。 @_在传参数的时候还在用,我的shift是依赖@_的值,不过前两行之后就空了。
  • 由于您的两个参数都有实际名称,我可以使用if (not defined $bar) 来查看两个参数是否都已通过。我也把它改成了否定的。这样,如果他们没有传递两个参数,您可以提前退出。这样一来,您的代码就少了一个缩进,并且您没有占用整个子例程的 if 结构。它使您更容易理解您的代码。
  • 我使用foreach my $item (@results) 而不是foreach (@results) 并依赖于$_。同样,您的程序在做什么更清楚,您不会将$_-&gt;[0]$_[0] 混淆(我认为这就是您所做的)。很明显你想要$item-&gt;[0]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-17
    • 2011-04-30
    • 1970-01-01
    • 2011-07-06
    • 1970-01-01
    • 2021-10-11
    • 2021-02-23
    • 2013-12-13
    相关资源
    最近更新 更多