12

perldoc -f each我们读到:

每个散列都有一个迭代器,由程序中的所有eachkeysvalues函数调用共享;它可以通过读取散列中的所有元素或通过评估keys HASHor来重置values HASH

当您离开包含 的作用域时,迭代器不会重置each(),这可能会导致错误:

my %h = map { $_, 1 } qw(1 2 3);
while (my $k = each %h) { print "1: $k\n"; last }
while (my $k = each %h) { print "2: $k\n"       }

输出:

1: 1
2: 3
2: 2

这种行为的常见解决方法是什么?一般情况下值得使用each吗?

4

8 回答 8

10

我认为只要您意识到这一点,它就值得使用。当您在迭代中需要键和值时,这是理想的:

while (my ($k,$v) = each %h) {
    say "$k = $v";
}

在您的示例中,您可以通过添加如下来重置迭代器keys %h;

my %h = map { $_ => 1 } qw/1 2 3/;
while (my $k = each %h) { print "1: $k\n"; last }
keys %h;  # reset %h
while (my $k = each %h) { print "2: $k\n" }

从 Perl 5.12each开始也将允许对数组进行迭代。

于 2010-03-07T14:32:17.817 回答
8

我发现each这样的成语非常方便:

my $hashref = some_really_complicated_method_that_builds_a_large_and_deep_structure();
while (my ($key, $value) = each %$hashref)
{
    # code that does stuff with both $key and $value
}

将该代码与此进行对比:

my $hashref = ...same call as above
foreach my $key (keys %$hashref)
{
    my $value = $hashref->{$key};
    # more code here...
}

在第一种情况下,两者$key$value都可以立即用于循环体。在第二种情况下,$value必须先获取。此外,键的列表$hashref可能非常庞大,这会占用内存。这有时是一个问题。each不会产生这样的开销。

然而,它的缺点each并没有立即显现:如果提前退出循环,哈希的迭代器不会被重置。另外(我发现这个更严重,甚至更不明显):你不能调用keys()values()或者each()来自这个循环中的另一个。这样做会重置迭代器,并且您将失去在 while 循环中的位置。while 循环将永远持续下去,这绝对是一个严重的错误。

于 2010-03-07T17:07:35.030 回答
8

each使用起来太危险了,许多风格指南完全禁止使用它。危险在于,如果一个循环each在散列结束之前中止,下一个循环将从那里开始。这可能会导致非常难以重现的错误;程序的一个部分的行为将取决于程序的完全不相关的其他部分。 可能使用each正确,但是曾经编写的每个可能使用您的哈希(或 hashref;它是相同的)的模块呢?

keys并且values总是安全的,所以只需使用它们。 keys无论如何,这使得以确定的顺序遍历散列变得更容易,这几乎总是更有用。( for my $key (sort keys %hash) { ... })

于 2010-03-08T04:22:31.070 回答
7

each 不仅值得使用,如果你想遍历所有对内存来说太大的绑定哈希,它几乎是强制性的。

开始循环之前的 void-context keys() (或值,但一致性很好)是唯一必要的“解决方法”;您是否有某种原因正在寻找其他解决方法?

于 2010-03-08T01:37:02.833 回答
2

使用该keys()函数重置迭代器。有关更多信息,请参阅常见问题解答

于 2010-03-07T14:36:19.540 回答
2

each有一个内置的、隐藏的全局变量会伤害你。除非您需要这种行为,否则只使用keys.

考虑这个例子,我们想要对我们的 k/v 对进行分组(是的,我知道printf这样做会更好):

#!perl

use strict;
use warnings;

use Test::More 'no_plan';

{   my %foo = map { ($_) x 2 } (1..15);

    is( one( \%foo ), one( \%foo ), 'Calling one twice works with 15 keys' );
    is( two( \%foo ), two( \%foo ), 'Calling two twice works with 15 keys' );
}

{   my %foo = map { ($_) x 2 } (1..105);

    is( one( \%foo ), one( \%foo ), 'Calling one twice works with 105 keys' );
    is( two( \%foo ), two( \%foo ), 'Calling two twice works with 105 keys' );
}


sub one {
    my $foo = shift;

    my $r = '';

    for( 1..9 ) {
        last unless my ($k, $v) = each %$foo;

        $r .= "  $_: $k -> $v\n";
    }
    for( 10..99 ) {
        last unless my ($k, $v) = each %$foo;

        $r .= " $_: $k -> $v\n";
    }

    return $r;
}

sub two {
    my $foo = shift;

    my $r = '';

    my @k = keys %$foo;

    for( 1..9 ) {
        last unless @k;
        my $k = shift @k;

        $r .= "  $_: $k -> $foo->{$k}\n";
    }
    for( 10..99 ) {
        last unless @k;
        my $k = shift @k;

        $r .= "  $_: $k -> $foo->{$k}\n";
    }

    return $r;
}

在实际应用程序中调试上述测试中显示的错误将非常痛苦。(为了更好的输出使用Test::Differences eq_or_diff而不是is。)

当然one()可以通过使用keys清除子程序开始和结束处的迭代器来修复。如果你记得。如果你所有的同事都记得。只要没有人忘记,它是绝对安全的。

我不了解你,但我会坚持使用keysand values

于 2010-03-08T08:16:03.687 回答
1

最好用它的名字: each。如果您的意思是“给我第一个键值对”或“给我前两个对”或其他什么,那么使用它可能是错误的。请记住,这个想法足够灵活,每次调用它时,都会得到下一对(或标量上下文中的键)。

于 2010-03-08T03:10:35.677 回答
1

如果您正在迭代绑定的哈希,例如包含数百万个键的数据库,each() 可能会更有效;这样你就不必加载内存中的所有键。

于 2010-03-08T12:28:32.020 回答