Perl:如何在不创建数组副本的情况下取消引用数组?

7
当我使用@$arrayRef或@{$arrayRef}解引用数组时,似乎会创建该数组的副本。有没有正确的解引用数组的方法?
这段代码...
sub updateArray1 {
        my $aRef = shift;
        my @a = @$aRef;
        my $aRef2 = \@a;

        $a[0] = 0;
        push(@a, 3);
        my $aRef3 = \@a;

        print "inside1 \@a: @a\n";
        print "inside1 \$aRef: $aRef\n";
        print "inside1 \$aRef2: $aRef2\n";
        print "inside1 \$aRef3: $aRef3\n\n";
}

my @array = (1, 2);

print "before: @array\n";
my $ar = \@array;
print "before: $ar\n\n";

updateArray1(\@array);

print "after: @array\n";
$ar = \@array;
print "after: $ar\n\n";

...具有输出...

before: 1 2
before: ARRAY(0x1601440)

inside1 @a: 0 2 3
inside1 $aRef: ARRAY(0x1601440)
inside1 $aRef2: ARRAY(0x30c1f08)
inside1 $aRef3: ARRAY(0x30c1f08)

after: 1 2
after: ARRAY(0x1601440)

正如您所看到的,@$aRef 创建了一个新的指针地址。

我发现解决这个问题的唯一方法是只使用引用:

sub updateArray2 {
        my $aRef = shift;

        @$aRef[0] = 0;
        push(@$aRef, 3);

        print "inside2 \@\$aRef: @$aRef\n";
        print "inside2 \$aRef: $aRef\n\n";
}

updateArray2(\@array);

print "after2: @array\n";
$ar = \@array;
print "after2: $ar\n\n";

这将产生输出:

inside2 @$aRef: 0 2 3
inside2 $aRef: ARRAY(0x1601440)

after2: 0 2 3
after2: ARRAY(0x1601440)

是否有可能对数组指针进行解引用而不复制整个数组?还是我需要保持它的引用形式,并在每次使用时对其进行解引用?

4个回答

13

在以下示例中,取消引用不会创建副本:

my @a = qw(a b c);
my $ra = \@a;
@{$ra}[0,1] = qw(foo bar);  # dereferencing is done here but not copying
print @$ra; # foo bar c
print @a; # foo bar c

相反,将(取消引用的)数组分配给另一个数组会创建副本

my @a = qw(a b c);
my $ra = \@a;
my @newa = @$ra;   # copy by assigning
$newa[0] = 'foo';
print @newa; # foo b c
print @a; # a b c

将一个数组赋值给另一个数组,基本上意味着所有来自旧数组的元素也应被赋值给新数组 - 这与仅为原始数组使用不同的名称不同。但是,将一个数组引用赋值给另一个数组只是使旧数组可用于具有不同名称的情况,即复制数组引用与复制数组内容不同。

请注意,这似乎与Python或Java等语言不同,因为在这些语言中变量仅描述数组对象,即对数组的引用而不是数组的内容。


5

使用实验性的refaliasing功能:

use 5.022;
use warnings;
use feature 'refaliasing';
no warnings 'experimental::refaliasing';
\my @array = $array_ref;

但为什么不将其保留作为参考呢?使用数组引用时,你可以做的任何事情都可以用数组来完成。


这应该被接受。与其他所有内容不同,这是正确的答案。(我刚刚了解到refaliasing并来到这里添加它到我的答案中,但你已经远远超过我了。) - piojo

3
代码明确要求复制数据。
my @a = @$aRef;

为了创建一个新数组@a
我不清楚你的意思是什么:
“是否有可能在不复制整个数组的情况下取消对数组指针的引用?”如果需要某些操作中的所有值-例如创建另一个数组、打印出来、发送到sortmap等,则数据可以通过指针进行复制,也可以不进行复制。如果数据被复制(即使只在堆栈上),那么它就与我们有一个数组一样,实际上已经“取消引用”了。
这取决于对数据的处理方式,一般而言无法提供更多信息。
如果需要访问特定元素(或片段)则直接取消引用即可,不要创建新数组。
但是,请不要使用@$aRef[0] = 0;,即使它是合法的,而是使用以下任一方法之一:
$$aRef[0] = 0;
$aRef->[0] = 0;

我认为第二个版本通常更安全,可以避免一些愚蠢的错误,并且更加清晰易懂。


不需要复制它们以打印或发送到排序或映射。 - ysth
@ysth 没有复制吗?难道不需要将值放在堆栈上才能使它们与其他内容一起工作吗?mapsort不是通过指针获取数据的吗?我并不是说会创建一个新数组,只是数据在幕后被复制(也许是逐个元素创建新列表)。感谢您的编辑。 - zdim
@ysth 我的意思是,如果需要这些值,无论是否为数组引用,它们都会被复制,而“取消引用”与此无关。我有一个数组引用,但当我将其提交给 sort 时,数据就会变得可用(在堆栈上?),就像我有一个数组一样。所以它被“取消引用了”。在其他操作中,它可能不会这样做——这取决于对数据的处理方式,一般情况下无法说太多。// 如果这是无意义的(或错误的),我希望知道。再次感谢您,但这与许多事情有着深刻的关系。 - zdim

2
我的观察和理解是,它与名称有关 - 如果您给数组取消引用一个名称,它将被复制,因为 Perl 变量是按副本传递的 - 它们与 C++ 中的结构体完全相同。但是,即使引用被复制(这类似于在 C/C++ 中的 int *newPtr = oldPtr),每个引用的副本都将指向同一对象。只有通过引用访问其元素或通过内联取消引用才会修改原始数组。我认为没有任何绕过此问题的方法。当您调用 my @foo = @{$barRef} 时,您正在调用复制构造函数。标量也是如此 - 当您通过 $_[0] = val 修改参数时,调用者的变量也会被修改。但是,如果您给变量命名,例如 my $arg = $_[0],则会创建一个副本。
话虽如此,还有一些方便的方法可以使用引用,如果您尚未使用它们,请了解以下内容:
$arrayRef->[0] = "foo";
$hashRef->{key} = "value";
$codeRef->(); # execute a code reference

但事实上,由于push无法处理引用,因此您需要像在示例中所做的那样使用内联解引用。

如果您真的非常想创建一个数组作为引用,有一种旧的Perl技术可以实现。但是在现代代码中不应使用它。这里描述了它:https://perldoc.perl.org/perlsub.html#Passing-Symbol-Table-Entries-(typeglobs)


谢谢你包含旧的技术 :)谁知道呢,也许有一天会派上用场。(可能性不大哈哈) - DemiImp

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