Perl 的集合数据类型────数组和哈希────允许你按整数下标或字符串键存储标量。Perl 5 的引用(references)则允许你通过特殊标量间接访问集合数据类型。Perl 中的嵌套数 据结构,例如数组的数组、哈希的哈希,是通过引用机制来实现的。
一个对数组的数组简单声明可能是:
……一个对哈希的哈希简单声明可能是:
访问嵌套数据结构中的元素需要用到 Perl 的引用语法。印记标示了欲取得数据的数量, 解引用箭头表明数据结构中的这部分值是一个引用:
对于嵌套数据结构这种情况,嵌套一个数据结构的唯一方法就是通过引用,因此箭头是 多余的。下面的代码和前面的等价,并且更清楚:
将嵌套数据结构作为第一等数组或哈希访问时,需要消歧代码块:
类似的,对嵌套数据结构分片也需要额外的标点:
空白的使用有助于,但不能完全消除这个语法结构的噪音。一些时候,使用临时变量会 更清晰:
perldoc perldsc
,数据结构的“食谱”,给出了有关如何使用 Perl 中各式数据结构 丰富的实例。
Perl 的表达力同样也扩展到了嵌套数据结构。当你试图编写一个嵌套数据结构组件时, 如果不存在,Perl 会创建通向这部分数据结构的路径:
第二行代码之后,这个数组的数组的数组的数组包含了对数组的引用的引用的引用的 引用。每一个引用包含一个元素。类似的,在嵌套数据结构中将未定义值作为哈希引 用会创建以合适的值作为键的中间哈希。
这个行为称为 自生,并且很有用。它的好处是减少嵌套数据结构的初始化代码。它的 坏处是无法区分创建嵌套数据结构中所缺元素究竟是有意还是无意。
CPAN 上的 autovivification
编译命令(pragmas)让你可以在词法作用域内对某 特定类型操作禁用自生行为。在多人参与的大型项目中很值得考虑这些问题。
Perl 5 的解引用语法的复杂结合多级引用潜在的迷惑性,使得调试嵌套数据结构变得困难。所幸 有两种可视化它们的好选择。
核心模块 Data::Dumper
可以将任意复杂的数据结构的值字符串化为 Perl 5 代码:
在识别数据结构所含内容以及找出应该访问到和实际访问到什么时很有用。Data::Dumper
可以转储对象和函数引用(如果你将 $Data::Dumper::Deparse
设置为真)。
Data::Dumper
是核心模块,并且打印出 Perl 5 代码,但它也给出详细的输出。一些开 发人员更愿意使用 YAML::XS
和 JSON
来调试程序。为理解它们的输出,你必须学习 不同的格式,但它们的输出更易阅读也更易理解。
Perl 5 的引用计数(reference_counts)内存管理系统对于用户代码来说有一个明星的 坏处。两个互指的引用最终形成了一 循环引用,Perl 无法自行销毁它。考虑生物模型, 每一个实体有父方母方,并可以有子代:
因为 $alice
和 $robert
都包含了一个指向 $cianne
数组引用,并且由于 $cianne
是一个包含 $alice
和 $robert
的哈希引用,Perl 始终无法将这 三者的引用计数减为零。它无法认识到循环引用的存在,并且无法管理这些实体的生存 期限。
你必须手动打断引用计数(通过清除 $alice
和 $robert
的子代或 $cianne
的 亲代),或者利用一个名为 弱引用 的特性。弱引用是一个不增加被引用者引用计数的 引用。弱引用可以通过核心模块 Scalar::Util
来使用。导出 weaken()
函数并对某 引用使用它可以防止引用计数的增加:
完成之后,$cianne
仍持有对 $alice
和 $robert
的引用,但是这些引用不会主动 阻拦 Perl 的垃圾回收器回收这些数据结构。经过正确设计的数据结构一般不会用到弱引用, 但在极少数情况下它们仍可能被用到。
不管数据结构嵌套得多深,Perl 都愿意处理,但是理解这些数据结构、理顺边边角角关系所花 的人力代价,别提访问数据结构各个部分所用的代码,就已经够高了。除了两三层嵌套的数据 结构,其他情况就应该考虑是不是应该用类和对象(moose)来对系统的各个组件进行建模, 它会更清楚地表达你的数据。
有的时候,将数据和合适的行为绑定在一起会使代码更清晰。
Hey! The above document had some coding errors, which are explained below:
- Around line 5:
-
A non-empty Z<>
- Around line 106:
-
A non-empty Z<>
- Around line 181:
-
A non-empty Z<>