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