perldoc -f 每个 我们读:

每个哈希都有一个迭代器,由所有哈希共享 each, keys, , 和 values 程序中的函数调用;可以通过读取散列中的所有元素或通过评估来重置它 keys HASH 或者 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 一般来说?

有帮助吗?

解决方案

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

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.12 开始 each 还允许对数组进行迭代。

其他提示

我发现 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 循环将永远持续下去,这绝对是一个严重的错误。

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

keysvalues 总是安全的,所以就使用它们。 keys 无论如何,使得以确定性顺序遍历哈希变得更容易,这几乎总是更有用。(for my $key (sort keys %hash) { ... })

每个不仅值得使用,而且如果您想循环遍历对于内存来说太大的绑定散列,则它几乎是强制性的。

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

使用 keys() 重置迭代器的函数。请参阅 常问问题 了解更多信息

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 在子例程开始和结束时清除迭代器。如果你记得。如果你所有的同事都记得的话。只要没有人忘记,就绝对安全。

我不了解你,但我会坚持使用 keysvalues.

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

如果您要迭代绑定散列(例如包含数百万个键的数据库),each() 可能会更有效;这样您就不必将所有密钥加载到内存中。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top