强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 9 章:引用与复杂数据结构

第 9 章:引用与复杂数据结构

“引用是 Perl 数据结构的基石”

引用(Reference)是 Perl 中最重要的高级特性之一。通过引用,你可以构建多维数组、嵌套哈希等复杂数据结构。


9.1 什么是引用?

引用是一个标量值,它指向另一个变量的内存地址。

use strict;
use warnings;

my $name = "Perl";

# 创建引用
my $ref = \$name;          # 反斜杠获取引用

print "$ref\n";            # SCALAR(0x55a1b2c3d4e5)
print $$ref, "\n";         # Perl(解引用)

# 引用是标量
print ref($ref), "\n";     # SCALAR(显示引用类型)
操作语法说明
创建引用\$var反斜杠获取引用
解引用$$ref两个 $ 表示标量引用
检查类型ref($ref)返回引用类型字符串

9.2 各种类型的引用

# 标量引用
my $scalar = 42;
my $sref   = \$scalar;
print $$sref, "\n";           # 42

# 数组引用
my @array  = (1, 2, 3);
my $aref   = \@array;
print $aref->[0], "\n";       # 1
print $$aref[1], "\n";        # 2(另一种写法)

# 哈希引用
my %hash   = (name => "Perl", year => 1987);
my $href   = \%hash;
print $href->{name}, "\n";    # Perl
print $$href{year}, "\n";     # 1987

# 子程序引用
my $code_ref = sub { return $_[0] * 2 };
print $code_ref->(5), "\n";   # 10

引用类型总结

原类型创建引用解引用箭头语法
标量 $x\$x$$ref-
数组 @a\@a@$ref$ref->[0]
哈希 %h\%h%$ref$ref->{key}
子程序 &f\&f&$ref$ref->()

9.3 匿名数据结构

匿名数组

# 匿名数组引用 [ ... ]
my $fruits = ["apple", "banana", "cherry"];

print $fruits->[0], "\n";    # apple
print $fruits->[-1], "\n";   # cherry
print scalar @$fruits, "\n"; # 3

# 匿名哈希引用 { ... }
my $person = {
    name => "张三",
    age  => 30,
    city => "北京",
};

print $person->{name}, "\n";  # 张三

为什么使用匿名结构?

# 不用匿名结构 —— 需要额外变量
my @temp = (1, 2, 3);
my $aref = \@temp;

# 用匿名结构 —— 更简洁
my $aref = [1, 2, 3];

# 哈希同理
my $href = { name => "Perl", version => 5.40 };

9.4 多维数据结构

二维数组

# 矩阵
my $matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

# 访问元素
print $matrix->[0][1], "\n";     # 2(第一行第二列)
print $matrix->[2][2], "\n";     # 9(第三行第三列)

# 遍历
for my $i (0..$#$matrix) {
    for my $j (0..$#{$matrix->[$i]}) {
        printf "%3d", $matrix->[$i][$j];
    }
    print "\n";
}

嵌套哈希

my $users = {
    "user1" => {
        name  => "张三",
        email => "zhangsan\@example.com",
        roles => ["admin", "editor"],
    },
    "user2" => {
        name  => "李四",
        email => "lisi\@example.com",
        roles => ["viewer"],
    },
};

# 访问
print $users->{user1}{name}, "\n";            # 张三
print $users->{user1}{roles}[0], "\n";        # admin

哈希数组(AoH)

# 数组的每个元素是哈希引用
my @employees = (
    { name => "张三", dept => "技术", salary => 15000 },
    { name => "李四", dept => "产品", salary => 12000 },
    { name => "王五", dept => "技术", salary => 18000 },
);

# 遍历
for my $emp (@employees) {
    printf "%-8s %-6s %d\n", $emp->{name}, $emp->{dept}, $emp->{salary};
}

# 按薪资排序
my @sorted = sort { $b->{salary} <=> $a->{salary} } @employees;

数组哈希(HoA)

# 哈希的每个值是数组引用
my %courses = (
    "张三" => ["数学", "物理", "化学"],
    "李四" => ["英语", "历史"],
    "王五" => ["数学", "英语", "编程"],
);

# 查看张三的课程
for my $course (@{$courses{"张三"}}) {
    print "  $course\n";
}

9.5 引用的内存模型

# 引用计数
my $ref1 = { name => "Perl" };   # 引用计数 = 1
my $ref2 = $ref1;                 # 引用计数 = 2
undef $ref1;                       # 引用计数 = 1
undef $ref2;                       # 引用计数 = 0 → 自动回收

# 循环引用会导致内存泄漏!
my $a = {};
my $b = {};
$a->{ref} = $b;   # $a 引用 $b
$b->{ref} = $a;   # $b 引用 $a → 循环引用!
# 即使 undef $a 和 $b,内存也不会释放

# 解决方案:使用弱引用(weaken)
use Scalar::Util qw(weaken);

my $parent = { name => "parent" };
my $child  = { name => "child" };
$parent->{child} = $child;
$child->{parent} = $parent;
weaken($child->{parent});    # 弱引用,不影响引用计数

9.6 Data::Dumper — 数据结构可视化

use Data::Dumper;

my $complex = {
    users => [
        { name => "张三", scores => [85, 90, 78] },
        { name => "李四", scores => [92, 88, 95] },
    ],
    config => {
        max_retry => 3,
        timeout   => 30,
    },
};

print Dumper($complex);

输出:

$VAR1 = {
          'config' => {
                        'timeout' => 30,
                        'max_retry' => 3
                      },
          'users' => [
                       {
                         'name' => '张三',
                         'scores' => [85, 90, 78]
                       },
                       {
                         'name' => '李四',
                         'scores' => [92, 88, 95]
                       }
                     ]
        };

美化输出

$Data::Dumper::Sortkeys = 1;   # 按键排序
$Data::Dumper::Indent   = 2;   # 缩进级别
print Dumper($complex);

9.7 复制引用 vs 深拷贝

use Storable qw(dclone);

# 浅拷贝(只复制引用)
my $original = { name => "Perl", tags => ["lang", "script"] };
my $shallow = $original;
$shallow->{name} = "Changed";
print $original->{name}, "\n";   # Changed — 原始也被修改了!

# 深拷贝(完全独立的副本)
my $original2 = { name => "Perl", tags => ["lang", "script"] };
my $deep = dclone($original2);
$deep->{name} = "Changed";
print $original2->{name}, "\n";  # Perl — 原始不受影响

9.8 业务场景:JSON 配置处理器

#!/usr/bin/env perl
use strict;
use warnings;
use JSON::XS;

# JSON 配置
my $json_text = '{
    "server": {
        "host": "0.0.0.0",
        "port": 8080,
        "workers": 4
    },
    "database": {
        "dsn": "dbi:SQLite:dbname=app.db",
        "options": {
            "auto_commit": 1,
            "raise_error": 1
        }
    },
    "logging": {
        "level": "info",
        "outputs": ["stdout", "file"]
    }
}';

# 解码为 Perl 数据结构
my $config = decode_json($json_text);

# 访问配置
print "服务器: $config->{server}{host}:$config->{server}{port}\n";
print "数据库: $config->{database}{dsn}\n";
print "日志级别: $config->{logging}{level}\n";

# 遍历日志输出
for my $output (@{$config->{logging}{outputs}}) {
    print "  输出到: $output\n";
}

# 修改并编码回 JSON
$config->{server}{port} = 9090;
my $updated_json = encode_json($config);
print "\n更新后的 JSON:\n$updated_json\n";

9.9 常见陷阱

陷阱 1:数组/哈希的扁平化

# 错误!数组会被扁平化
my @matrix = ([1,2,3], [4,5,6]);
my @flat   = @matrix;        # 变成 6 个元素!

# 正确:使用引用
my $matrix = [[1,2,3], [4,5,6]];

陷阱 2:自动解引用(Perl 5.24+)

# Perl 5.24+ 允许这种写法
my $aref = [1, 2, 3];
push $aref->@*, 4;          # 后缀解引用(Perl 5.24+)

# 旧写法
push @$aref, 4;              # 也可以

本章小结

要点内容
引用标量值,指向其他数据的地址
[]匿名数组引用
{}匿名哈希引用
->箭头解引用
ref()检查引用类型
深拷贝Storable::dclone()
Data::Dumper数据结构可视化

练习

  1. 创建一个二维数组引用(3x3 矩阵),打印对角线元素
  2. 创建一个嵌套哈希,表示学校→班级→学生的结构
  3. 使用 JSON::XS 编码/解码一个包含用户列表的复杂数据结构
  4. 演示循环引用导致的内存问题,并使用 Scalar::Util::weaken 修复
  5. 实现一个树形数据结构(每个节点有 name 和 children),并递归打印

扩展阅读