引用和嵌套数据结构

列表无法包含列表作为其元素,array也同样无法包含其他array和hash作为其元素,它们只能包含scalar。看看我们尝试下面的做法会发生什么:

  1. my @outer = ("Sun", "Mercury", "Venus", undef, "Mars");
  2. my @inner = ("Earth", "Moon");
  3. $outer[3] = @inner;
  4. print $outer[3]; # "2"

$outer[3]是个scalar,因此它需要一个scalar值。当你尝试将@inner这样的array值赋给它,@inner就会在scalar上下文中被求值,这就与将scalar @inner是同样的效果。这相当于求出了array @inner的长度,也就是2。

然而,scalar变量可以包含任何变量的引用,包括array和hash。在Perl中,复杂的数据结构就是这样被构造出来的。

我们用反斜杠来创建一个引用。

  1. my $colour = "Indigo";
  2. my $scalarRef = \$colour;

如果你能够使用某个变量名,你可以加一些花括号,把一个变量的引用放进去。

  1. print $colour; # "Indigo"
  2. print $scalarRef; # 输出可能是 "SCALAR(0x182c180)"
  3. print ${ $scalarRef }; # "Indigo"

如果结果没有歧义的话,你甚至可以直接省略掉花括号:

  1. print $$scalarRef; # "Indigo"

如果是一个对array或者hash的引用,你可以用花括号或者更加风靡的箭头运算符->

  1. my @colours = ("Red", "Orange", "Yellow", "Green", "Blue");
  2. my $arrayRef = \@colours;
  3. print $colours[0]; # 直接访问array元素
  4. print ${ $arrayRef }[0]; # 通过引用访问array元素
  5. print $arrayRef->[0]; # 与上一句等价
  6. my %atomicWeights = ("Hydrogen" => 1.008, "Helium" => 4.003, "Manganese" => 54.94);
  7. my $hashRef = \%atomicWeights;
  8. print $atomicWeights{"Helium"}; # 直接访问hash元素
  9. print ${ $hashRef }{"Helium"}; # 通过引用访问hash元素
  10. print $hashRef->{"Helium"}; # 与上一句等价 - 这种写法相当常见

声明数据结构

这里有4个例子,不过现实中最后一个最有用。

  1. my %owner1 = (
  2. "name" => "Santa Claus",
  3. "DOB" => "1882-12-25",
  4. );
  5. my $owner1Ref = \%owner1;
  6. my %owner2 = (
  7. "name" => "Mickey Mouse",
  8. "DOB" => "1928-11-18",
  9. );
  10. my $owner2Ref = \%owner2;
  11. my @owners = ( $owner1Ref, $owner2Ref );
  12. my $ownersRef = \@owners;
  13. my %account = (
  14. "number" => "12345678",
  15. "opened" => "2000-01-01",
  16. "owners" => $ownersRef,
  17. );

显然可以不用这么费劲,这段代码可以简化为:

  1. my %owner1 = (
  2. "name" => "Santa Claus",
  3. "DOB" => "1882-12-25",
  4. );
  5. my %owner2 = (
  6. "name" => "Mickey Mouse",
  7. "DOB" => "1928-11-18",
  8. );
  9. my @owners = ( \%owner1, \%owner2 );
  10. my %account = (
  11. "number" => "12345678",
  12. "opened" => "2000-01-01",
  13. "owners" => \@owners,
  14. );

用不同的符号声明匿名的array和hash也是可行的。用方括号声明匿名array,而用花括号声明匿名hash,这两种方法返回的是声明的匿名数据结构的引用。看仔细了,下面的代码声明的%account和上面的完全等价:

  1. # 花括号表示匿名hash
  2. my $owner1Ref = {
  3. "name" => "Santa Claus",
  4. "DOB" => "1882-12-25",
  5. };
  6. my $owner2Ref = {
  7. "name" => "Mickey Mouse",
  8. "DOB" => "1928-11-18",
  9. };
  10. # 方括号表示匿名array
  11. my $ownersRef = [ $owner1Ref, $owner2Ref ];
  12. my %account = (
  13. "number" => "12345678",
  14. "opened" => "2000-01-01",
  15. "owners" => $ownersRef,
  16. );

或者写得更加简短(这也是你真正应该用来声明复杂数据结构的方法):

  1. my %account = (
  2. "number" => "31415926",
  3. "opened" => "3000-01-01",
  4. "owners" => [
  5. {
  6. "name" => "Philip Fry",
  7. "DOB" => "1974-08-06",
  8. },
  9. {
  10. "name" => "Hubert Farnsworth",
  11. "DOB" => "2841-04-09",
  12. },
  13. ],
  14. );

从数据结构中获取信息

现在我们假设你还在折腾那个%account,而且其他东西都不在作用域内(如果还有其他东西的话)。你可以逆向操作解引用,以取得每一项需要打印的信息。同样,这里有4个例子,其中最后一个最有用:

  1. my $ownersRef = $account{"owners"};
  2. my @owners = @{ $ownersRef };
  3. my $owner1Ref = $owners[0];
  4. my %owner1 = %{ $owner1Ref };
  5. my $owner2Ref = $owners[1];
  6. my %owner2 = %{ $owner2Ref };
  7. print "Account #", $account{"number"}, "\n";
  8. print "Opened on ", $account{"opened"}, "\n";
  9. print "Joint owners:\n";
  10. print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
  11. print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或者写得更简短些:

  1. my @owners = @{ $account{"owners"} };
  2. my %owner1 = %{ $owners[0] };
  3. my %owner2 = %{ $owners[1] };
  4. print "Account #", $account{"number"}, "\n";
  5. print "Opened on ", $account{"opened"}, "\n";
  6. print "Joint owners:\n";
  7. print "\t", $owner1{"name"}, " (born ", $owner1{"DOB"}, ")\n";
  8. print "\t", $owner2{"name"}, " (born ", $owner2{"DOB"}, ")\n";

或者使用引用和->运算符:

  1. my $ownersRef = $account{"owners"};
  2. my $owner1Ref = $ownersRef->[0];
  3. my $owner2Ref = $ownersRef->[1];
  4. print "Account #", $account{"number"}, "\n";
  5. print "Opened on ", $account{"opened"}, "\n";
  6. print "Joint owners:\n";
  7. print "\t", $owner1Ref->{"name"}, " (born ", $owner1Ref->{"DOB"}, ")\n";
  8. print "\t", $owner2Ref->{"name"}, " (born ", $owner2Ref->{"DOB"}, ")\n";

如果我们完全跳过那些中间值,代码看起来就是这样:

  1. print "Account #", $account{"number"}, "\n";
  2. print "Opened on ", $account{"opened"}, "\n";
  3. print "Joint owners:\n";
  4. print "\t", $account{"owners"}->[0]->{"name"}, " (born ", $account{"owners"}->[0]->{"DOB"}, ")\n";
  5. print "\t", $account{"owners"}->[1]->{"name"}, " (born ", $account{"owners"}->[1]->{"DOB"}, ")\n";

如何用array的引用作茧自缚

这个数组有5个元素:

  1. my @array1 = (1, 2, 3, 4, 5);
  2. print @array1; # "12345"

然而这个array只有1个元素(一个含有5个元素的匿名array的引用):

  1. my @array2 = [1, 2, 3, 4, 5];
  2. print @array2; # e.g. "ARRAY(0x182c180)"

这个scalar是一个含有5个元素的匿名array的引用:

  1. my $array3Ref = [1, 2, 3, 4, 5];
  2. print $array3Ref; # e.g. "ARRAY(0x22710c0)"
  3. print @{ $array3Ref }; # "12345"
  4. print @$array3Ref; # "12345"