在 Perl 中,未初始化的哈希键是否具有默认值零?

8

我有类似以下的Perl代码:

# -- start --

my $res;

# run query to fetch IPv6 resources
while( my $row = $org_ip6_res->fetchrow_arrayref )
{
    if( $row->[4] =~ /PA/ ) {
        $res->{ipv6}{pa}{$row->[2]}++;
    } elsif( $row->[4] eq 'PI' ) {
        $res->{ipv6}{pi}{$row->[2]}++;
    }
}

# -- stop --

在迭代查询结果之前,$res 从未被设置,但代码仍然可以正常运行。

当我在每个值之前放置 print 语句时,两种情况下都会得到空白,但如果打印语句在增量应用之后出现,根据组织拥有的 IPv6 资源数量,将得到一个大于等于1的值。

我的问题是,我是否应该理解为,在 Perl 中,未初始化的哈希键自动具有零值?

如果这看起来像新手问题,对不起,我只是不熟悉这样的结构,即 $hashref->{foo}->{bar}++,其中尚未明确分配给 $hashref->{foo}->{bar} 值。提前谢谢!

4个回答

26

这个值不会自动归零。初始状态下该值为未定义。但是,如果你将它视为数字(例如应用 ++),那么Perl会将其视为零。如果你将它视为字符串(例如应用 .),那么Perl会将其视为空字符串。

perldoc perlsyn 中的“Declarations”部分:

在Perl中,你需要声明的只有报告格式和子程序(有时甚至不需要声明子程序)。变量保存未定义值(“undef”),直到被赋予了定义的值,即任何非“undef”的值。当作为数字使用时,“undef”被视为0;当作为字符串使用时,它被视为空字符串“”,当作为未分配的引用时,它被视为错误。


5
为了解释 Telemachus 的帖子,未初始化的值将是未定义的。结构的深层部分是 autovivified。这是一个方便的功能,可以自动为您创建数据结构。当您需要它时,自动存活是很棒的,但是当您想要防止它时,它可能会很麻烦。在网络上有许多教程、文章和帖子,可以帮助理解自动存活。
因此,对于给定的未定义 $ref$ref->{ipv6}{pa}{'foo'}++$ref 将被赋予一个值:
$ref = { 
     ipv6 => { 
          pa => { 
              foo => undef
          }
     }
};

然后undef将会被增加,因为undef数值化为0,我们得到0++,即1。 最终结果为:ref->{ipv6}{pa}{'foo'} == 1
如果你启用了警告(你使用了use warnings;,对吧?),当你操作这些未定义的值时,你会收到一个“未初始化的值”警告。如果增加未初始化的值是期望的行为,那么你可以在代码的有限部分关闭所需的警告组:
use strict;
use warnings;
my $res;

// run query to fetch IPv6 resources
while( my $row = $org_ip6_res->fetchrow_arrayref )
{   no warnings 'uninitialized';
    if( $row->[4] =~ /PA/ ) {
        $res->{ipv6}{pa}{$row->[2]}++;
    } elsif( $row->[4] eq 'PI' ) {
        $res->{ipv6}{pi}{$row->[2]}++;
    }
}

您可以在perllexwarn中找到警告层次结构。


3
++和--运算符不会警告未初始化值的使用。相反,它们会将undef静默转换为0。 - Michael Carman
实际上,我记得看到了daotoad描述的相同行为,即在自动递增时收到“未初始化值”警告。你们两个知道这是发生在哪个Perl版本中的吗? - j_random_hacker
1
@j_random - 我刚刚用 Perl 5.8 进行了测试,Michael Carman 是正确的。perl -w -e '$foo{bar}++' 不会警告未初始化的值。perl -w -e '$foo{bar} += 2' 也不会。.=, '-=` 和 '--' 也没问题。然而,其他 X= 操作符会生成警告。 - daotoad
谢谢!可能是一些奇怪的东西,比如 $foo{bar}++ 可以正常工作,但 ++$foo{bar} 不行。我希望我能回忆起确切的情况... - j_random_hacker

4

这基本上是未定义的,但在递增时会被视为零。

在Perl术语中,这个术语是“自动创建”的。

您可能想要做的是使用 exists 关键字

$res->{ipv6}{pa}{$row->[2]}++ if exists($res->{ipv6}{pa}{$row->[2]});

exists关键字测试哈希表中是否存在键,而不是值是否未定义。use Test::More tests => 4; my %h = ( 'a' ); ok( exists $h{a} ); ok( !defined $h{a} ); ok( !exists $h{b} ); ok( !defined $h{b} ); - Axeman
我知道。关键是他需要理解自动创建和存在但未定义的键之间的区别,以及如何处理每个键 - 如果他遵循我提供的链接并阅读exists关键字的文档,他将理解所有这些内容。 - Robert S. Barnes

2
“未初始化的哈希键”是不存在的。可以未初始化的是特定键的值。哈希值只是一个标量值;它与变量(例如$foo)没有区别。
在您的示例中,有几个不同的Perl功能交互。最初,$res未定义(即其值为undef)。当您将未初始化的值用作哈希引用时(如$res->{ipv6}...),Perl会将其自动创建为一个哈希表。也就是说,Perl创建了一个匿名哈希,并将undef的值替换为对新哈希的引用。每次使用结果值作为引用时,此过程都会重复(静默地)发生。
最终,您通过自动创建到$res->{ipv6}{pa}{$row->[2]}的路径,该值为未定义。请记住,这只是一个标量值,就像$foo一样。其行为与以下语句相同:
my $foo;
$foo++;

Perl在使用未定义值时有特殊的处理方式。如果您将其用作数字,Perl会将它们转换为0。如果您将其用作字符串,Perl会将它们转换为空字符串''。在大多数情况下,如果启用了警告(应该启用),您将得到一个“使用未初始化的值...”警告。然而,auto-increment运算符(++)是一个特殊情况。为方便起见,在递增之前,它会将值从undef静默转换为0

非常正确,我实际上指的是特定键的未初始化<i>值</i>。:) 谢谢您的解释。 - freakwincy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接