无法理解Perl哈希引用

时间:2018-07-15 13:22:41

标签: perl reference

我的Perl项目可以在多个系统上运行,但是我不希望它们在不知道其他系统正在做什么的情况下独立运行。我需要使它们的数据文件或多或少保持同步,这意味着要合并它们的内容,因此我需要一个数据结构来包含每台计算机的详细信息以及对另一台计算机的引用。

正是这种引用导致了我的麻烦。

如果我将其简化为仅两台PC

<html>
    <body>
        <ul>
            <li>foo</li>
            <li>bar</li>
            <li>baz</li>
        </ul>
    </body>
</html>

这给出了以下内容

  

#perl test.pl
      参考文献在test.pl第43行处找到了偶数大小的列表。
      参考文献找到了在test.pl第43行处期望的大小均匀的列表。

如何在对等字段中设置引用,以后可以取消引用?

3 个答案:

答案 0 :(得分:6)

如果您想很好地介绍Perl中的引用,建议使用perldoc perlreftut。在您列出的语言中,C及其指针最接近Perl的引用(除非没有“引用算术”或无效的引用)。特别地,取消引用是Perl中的显式操作。

您正在尝试构建循环结构:$PC1包含对$PC2的引用,而$PC2包含对$PC1的引用。我不建议这样做,因为Perl的自动内存管理基于引用计数。如果创建参考循环,则在您手动中断该循环之前,该参考循环不会自动释放。 (您可以通过明智地使用weak references来解决此问题,但是需要考虑哪些引用应该“拥有”结构,而哪些引用不应该拥有。)

实现此目的最简单的方法可能是保持数据结构不变,但将其放入哈希中:

my %PEERS = (
    PC1 => {
        name    => 'PC1',
        os      => 'Linux',
        data    => '/its/data/directory',
        peer    => 'PC2',
    },
    PC2 => {
        name    => 'PC2',
        os      => 'MSWin32',
        data    => 'X:\\its\\data\\directory',
        peer    => 'PC1',
    },
);

然后您可以编写如下代码:

for my $name (sort keys %PEERS) {
    my $THIS = $PEERS{$name};
    my $PEER = $PEERS{$THIS->{peer}};
    if ($THIS->{name} eq hostname && $THIS->{os} eq $^O) {
        print $THIS->{name} . ', ' . $PEER->{name} . "\n";
        last;
    }
}

当然,您现在已经有效地存储了两次名称(一次是哈希键,一次是(子)哈希中的字段),因此您可能希望将其更改为:

my %PEERS = (
    PC1 => {
        os      => 'Linux',
        data    => '/its/data/directory',
        peer    => 'PC2',
    },
    PC2 => {
        os      => 'MSWin32',
        data    => 'X:\\its\\data\\directory',
        peer    => 'PC1',
    },
);

if (my $THIS = $PEERS{hostname()}) {
    if ($THIS->{os} eq $^O) {
        print "$THIS->{name}, $THIS->{peer}\n";
    }
}

这为我们节省了一个循环,因为我们可以直接通过对等方的主机名查找它们。 当然,这假定您的对等名称是唯一的。


也就是说,如果您确实想要,您可以尝试进行尝试:

# my $PEERS = [ $PC1, $PC2 ];
# I'm not sure why you're using a reference to an array here.
# I'd just use an array, which saves us some dereferencing below:
my @PEERS = ($PC1, $PC2);

for my $Peer ( @PEERS ) {
    # my %Peer = %$p;
    # This line makes a copy of the %$p hash.
    # We want to modify %$p directly, so let's not do that.

    if ($Peer->{name} eq 'PC1') {
        $Peer->{peer} = $PC2;
    }
    else {
        $Peer->{peer} = $PC1;
    }
    # %$p = %Peer;    # I was surprised to find this line is necessary, otherwise the changes are lost
    # We don't need to copy the modified hash back because now we're modifying %$p directly.
}

类似地,

for my $p ( @$PEERS ) {
    my %THIS = %$p;
    my %PEER = $THIS{peer};

应该成为

for my $THIS ( @PEERS ) {
    my $PEER = $THIS->{peer};

无需将%$p复制到%THIS,并且由于将$THIS{peer}(引用)分配给%PEER(哈希)而收到警告。如果您真的想在此处进行复制,则必须使用%PEER = %{ $THIS->{peer} }(与%{ $p }一样取消引用(在原始代码中简称为%$p))。

答案 1 :(得分:1)

您在这里有两个不同的问题。

首先,在哈希中设置peer值的代码的复杂性。您的代码有效,但我认为,如果a)停止将PC详细信息存储在单个标量中,并且b)停止创建不必要的取消引用的哈希副本,我们可以简化代码。

my $PCs = {
  PC1 => {
    name    => 'PC1',
    os      => 'Linux',
    data    => '/its/data/directory',
    peer    => 'PC2',
  },
  PC2 => {
    name    => 'PC2',
    os      => 'MSWin32',
    data    => 'X:\\its\\data\\directory',
    peer    => 'PC1',
  },
};

然后我们可以使用以下代码连接peer值:

for (values %$PCs) {
  if (exists $PCs->{$_->{peer}) {
    $_->{peer} = $PCs->{$_->{peer}};
  } else {
    warn "PC $_->{name} has an unknown peer: $_->{peer}\n";
  }
}

但这只是编写现有代码的一种更干净的方法。 PC最终以完全相同的方式连接在一起。

然后是您的错误消息。这根本不是由您的数据结构引起的。这是由于以下原因造成的:

my %PEER = $THIS{peer};

您正在尝试使用哈希引用初始化哈希。那是行不通的。您需要取消引用哈希。

my %PEER = %{ $THIS{peer} };

但是我们根本不需要%PEER变量(或%THIS)。

for my $p ( values %$PCs ) {
  if ( ($p->{name} eq hostname) && ($p->{os} eq $^O) ) {
    print "$p->{name}, $p->{peer}{name}\n";
    last;
  }
}

答案 2 :(得分:0)

非常感谢您的投入,对于无法尽快回复我们深表歉意。我已经相应地更改了测试文件,它现在可以工作,但是您可能需要注意额外的修复程序,以使其能够与Windows上的Active Perl v5.24.1一起使用。因此,我认为最好是对自己的问题发布另一个答案,而不是发表评论或其他内容。成功的测试文件现在显示为:

use strict;
use warnings;
use Sys::Hostname;

# Peer computers
my $PC1 =
    {
    name    => 'PC1',
    os      => 'Linux',
    data    => '/its/data/directory',
    peer    => 'PC2' #  But whas is really needed here is a reference to PC2's data structure below
    };

my $PC2 =
    {
    name    => 'PC2',
    os      => 'MSWin32',
    data    => 'X:\\its\\data\\directory',
    peer    => 'PC1' #  But whas is really needed here is a reference to PC1's data structure above
    };

my @PEERS = ( $PC1, $PC2 );

# Some code to set up the peer fields, for example
for my $p ( @PEERS )
    {
    if( $p->{name} eq $PC1->{name} )
        {
        $p->{peer} = $PC2;
        }
    else
        {
        $p->{peer} = $PC1;
        }
    }

# Debug check  -  dump the fields
for my $p ( @PEERS )
    {
    my %This = %{$p};               # Fix needed for MSWin32, not needed for Linux
    for my $f ( sort(keys %This) )
        {
        print( $f.' = '.($f eq 'peer' ? $p->{$f}->{name} : $p->{$f})."\n" );
        }
    print( "\n" );
    }

# Determine which system we are, and which we should pair with by default
for my $p ( @PEERS )
    {
    my $THIS = $p;
    my $PEER = $THIS->{peer};
    if( ($THIS->{name} eq hostname) && (lc($THIS->{os}) eq lc($^O)) )
        {
        print( $THIS->{name}.', '.$PEER->{name}."\n" );
        last;
        }
    }