无法理解Perl哈希引用

无法理解Perl哈希引用,perl,reference,Perl,Reference,我的Perl项目可以在多个系统上运行,但我不希望它们在不知道其他系统在做什么的情况下孤立运行。我需要保持他们的数据文件或多或少的同步,这意味着合并他们的内容,所以我需要一个数据结构来包含每台计算机的详细信息以及对另一台计算机的引用,这是它的对等计算机 正是这种引用给我带来了问题 如果我把它简化为两台电脑 use strict; use warnings; use Sys::Hostname; # Peer computers my $PC1 = { name => 'PC1',

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

正是这种引用给我带来了问题

如果我把它简化为两台电脑

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 ) {

    my %Peer = %$p;

    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
}

# 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 ) && ( $THIS{os} eq $^O ) ) {
        print( $THIS{name}.', '.$PEER{name}."\n" );
        last;
    }
}
这就产生了以下结果

#perl test.pl
在test.pl第43行预期的偶数大小列表中找到参考。
在test.pl第43行预期的偶数大小列表中找到了参考


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

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

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

实现这一点的最简单方法可能是保持数据结构不变,但将其放在散列中:

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
)。

这里有两个不同的问题

首先,您的代码在哈希中设置
对等
值的复杂性。您的代码可以工作,但我认为如果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',
  },
};
然后我们可以用如下代码连接
对等
值:

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


非常感谢您的投入,并为没有尽快回复而道歉。我已经相应地更改了测试文件,现在它可以工作了,但是您可能需要注意额外的修复,以使它能够在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;
        }
    }

注意:
my%Peer=%$p
将复制散列,如果您保留引用
my$Peer=$p
或只是使用
$p
,并通过引用修改散列值,则以后无需更新整个散列。例如,您可以执行
$p->{peer}=$PC1
而不是复制整个哈希:
%$p=%peer
。有关Perl引用的更多信息,请参阅您收到的警告来自以下行:
my%PEER=$this{PEER}
。此处
$THIS{peer}
是一个哈希引用,但赋值的左侧不是引用。因此,您需要
my%PEER=%{$THIS{PEER}}
如果删除第一段并将标题更改为更具技术性(与问题相关)的内容,我觉得您的问题可能会在这里得到更好的理解。正如网站上的其他人所说:“标题应该试图描述问题,而不是你的感受。”——)我认为,在第一个
for
循环中使用命名的循环控制变量
$pc
,就像在第二个循环中一样,这样做会有好处。
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;
        }
    }