Perl引用:自信地创建、取消引用和调试

学习Perl的引用是Perl程序员的一个必经之路。在你“理解”引用之前,语言的大部分内容对你来说都是陌生的。引用有它自己的特殊语法和规则,这可能会让它们看起来很奇怪,难以理解。好消息是引用的核心功能很容易学习和使用。本文描述了使用引用的主要方法以及一些实用的工具,以帮助你摆脱困境。所以即使你对引用并不完全舒服,你也能编写出能工作的代码。

什么是引用?

引用是一个标量变量,其值是指向另一个Perl变量的指针。如果你打印出一个引用,你会看到类似以下的内容

SCALAR(0x509ea0)

值看起来像内存地址,但实际上它是Perl的一个内部键,它指向另一个变量。引用可以指向Perl的任何变量类型:标量、数组、哈希、文件句柄、子程序和全局变量。引用很有用,因为它们

  • 节省内存 - 当你只需要一个变量时,为什么还要创建两个相同的变量的副本?
  • 使子程序能够返回非标量或列表格式的值。(引用是标量指针,可以指向任何格式的值)。
  • 可以封装由嵌套数组、哈希、标量等组成的复杂数据结构。

访问引用指向的值被称为“取消引用”。当你取消引用一个引用时,Perl将获取引用指向的实际变量,而不仅仅是它的指针的值。需要取消引用引用变量以使用其底层值是引用的主要缺点;直接访问变量总是更快。

声明和访问引用

我们将重点关注数组和哈希引用,因为它们是最常见的引用类型。处理它们很容易。对于数组,使用方括号而不是圆括号来声明,并使用箭头操作符(“->”)来取消引用

my @array       = ('apple', 'banana', 'pear');
my $array_ref   = ['apple', 'banana', 'pear'];

print $array[1];       #banana
print $array_ref->[1]; #banana

对于哈希,使用花括号而不是圆括号来声明,并使用相同的箭头操作符来取消引用

my %hash        = (one => 1, two => 2, three => 3);
my $hash_ref    = {one => 1, two => 2, three => 3};

print $hash{three};       #3
print $hash_ref->{three}; #3

引用最酷的事情之一是能够创建复杂的数据结构来存储任何你需要的数据。让我们看看一个虚构的客户数据结构的更现实的例子

my $customer = { name   => 'Mr Smith',
                 dob    => '01/18/1987',
                 phones => { home   => '212-608-5787',
                             work   => '347-558-0352'},
                 last_3_purchase_values => [ 78.92, 98.36, 131.00 ],
                 addresses => [ {   street => '37 Allright Ave',
                                    zip    => '11025',
                                    city   => 'New York',
                                    state  => 'NY',
                                }, 
                                {   street => '23 Broadway',
                                    zip    => '10125',
                                    city   => 'New York',
                                    state  => 'NY',
                                },
                               ],
};

$customer是一个包含5个键的哈希引用。其中两个键(“name”和“dob”)有常规的标量值。但是,其他键值是嵌套的引用:“phones”是一个嵌套的哈希引用,而“last_3_purchase_values”和“addresses”是数组引用。那么你将如何访问$customer数据结构中的任何值?看看这个

print $customer->{name}; # Mr Smith
print $customer->{phones}{home}; # 212-608-5787
print $customer->{last_3_purchase_values}[0]; # 78.92
print $customer->{addresses}[1]{street}; # 23 Broadway

要取消引用$customer中的值,我们首先使用箭头操作符。然后,我们添加所需的键或索引以访问下一级别的数据。处理引用的主要挑战是理解你正在取消引用的数据类型:如果是数组,你需要使用数组访问器语法“[#]”,而如果是哈希,你需要传递键,放在花括号“{key_value}”中。

处理数组引用

有时你需要遍历数组引用。这种语法与普通数组相同,只是你需要取消引用整个数组,而不仅仅是它的单个元素。这是通过将数组引用放在取消引用的数组块中完成的:“@{ $array_ref }”。让我们看看使用$customer的一些例子

use feature 'say';

#iterate through a nested array
foreach my $purchase_value (@{ $customer->{last_3_purchase_values} }) {
    say $purchase_value;
}

#iterate through a nested array and dereference and print the street
foreach my $address (@{ $customer->{addresses} }) {
    say $address->{street};
}

数组支持其他操作,如 push 和 shift。在这些情况下,您还需要一个解除引用的数组块。

push @{$customer->{addresses}}, { street => '157 Van Cordant Street',
                                  zip    => '10008',
                                  city   => 'New York',
                          state  => 'NY',
                                 };

在这里,我们在“addresses”数组引用上推送了一个新地址。我们使用了解除引用的数组块来解除引用“addresses”,以便我们可以对其执行 push 操作。

处理散列引用

解除引用块也可以用于散列操作。最常见操作可能是通过“keys”函数遍历散列的键。在这种情况下,您需要使用解除引用的散列块“%{ $hash_ref }”。让我们看看使用 $customer 的一个示例。

use feature 'say';

# iterate through a nested hash
foreach my $key (keys %{ $customer->{phones} }) {
    say $customer->{phones}{$key};
}

故障排除引用

引用可能比普通变量更难调试,因为您需要解除引用才能看到它指向哪个变量。想象一下,您想要打印出 $customer 的内容。这不起作用

print $customer; # HASH(0x2683b30)

幸运的是,您可以使用 Data::Dumper 的“Dumper”函数来为您解除引用并格式化打印引用。

use Data::Dumper;

print Dumper($customer);

将打印

$VAR1 = {
          'last_3_purchase_values' => [
                                        '78.92',
                                        '98.36',
                                        '131'
                                      ],
          'dob' => '01/18/1987',
          'addresses' => [
                           {
                             'city' => 'New York',
                             'zip' => '11025',
                             'street' => '37 Allright Ave',
                             'state' => 'NY'
                           },
                           {
                             'city' => 'New York',
                             'zip' => '10125',
                             'street' => '23 Broadway',
                             'state' => 'NY'
                           },
                           {
                             'city' => 'New York',
                             'zip' => '10008',
                             'street' => '157 Van Cordant Street',
                             'state' => 'NY'
                           }
                         ],
          'name' => 'Mr Smith',
          'phones' => {
                        'work' => '347-558-0352',
                        'home' => '212-608-5787'
                      }
        };

另一个有用的工具是 Perl 的 ref 函数。只需将引用变量传递给 ref,它将返回引用指向的变量类型。

从变量创建引用

要创建现有变量的引用,请使用反斜杠操作符。

my $array_ref   = \@array;
my $hash_ref    = \%hash;

反斜杠操作符通常在子程序内部使用。例如,考虑以下三个子程序

# example 1 - processor & memory inefficient
sub return_array {
    my @array = (1, 2, 3);
    foreach my $element (@array) {
        calculate($element);
    }
    return @array;
}

# example 2 - processor inefficient
sub return_array {
    my $array = [1, 2, 3];
    foreach my $element (@$array) {
        calculate($element);
    }
    return $array;
}

# example 3 - best option
sub return_array {
    my @array = (1, 2, 3);
    foreach my $element (@array) {
        calculate($element);
    }
    return \@array;
}

所有这些子程序都试图做同样的事情 - 声明一个数组,遍历它,然后返回它。示例 1 将返回数组的元素列表。这效率不高,因为列表由原始数组元素的标量副本组成,这意味着:Perl 创建副本,返回它们,然后当它们超出作用域时丢弃原始数组。

示例 2 的主要缺点是,由于以引用开始,Perl 必须解除引用数组才能遍历它,这是计算资源的浪费。示例 3 没有这些缺点,因为它以数组开始,遍历它,然后返回数组的引用。这是流行的 Perl 编程模式。

结论

如果引用之前对您来说是个谜,希望这篇文章能帮助您有信心地使用它们。您对引用饥渴吗?它们的内容远不止这里所描述的。查看 Perl 的官方文档 perldoc,它有一个 教程 和更多 详细指南。一本关于引用的精彩书籍是《Intermediate Perl》( affiliate link),它有超过 100 页关于引用的内容。

喜欢这篇文章吗?帮助我们,转发 它!


这篇文章最初发布在 PerlTricks.com

标签

David Farrell

David 是一位专业程序员,他经常 推文博客 关于代码和编程艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请通过在 GitHub 上打开问题或拉取请求来帮助我们。