Perl语言应该记住的最重要的一点是:Perl中的数组和哈希表始终是一维的。因此,数组和哈希表只保存标量值,不直接存贮数组或其它的复杂数据结构。数组的成员要么是数(或字符串)要么是引用。
- 中括号可以创建匿名数组的引用,所以创建多维数组可以这样来创建:如
$line = ['solid' , 'black' , ['1','2','3'] , ['4','5','6']];
$line->[0] = solid
$line->[1] = black
$line->[2][0] = 1
$line->[3][1] = 5
- 大括号可以创建匿名 hash表的引用,所以创建多维数组还可以这样来创建多维数组
$map = {”1″ =>
{
”1-0″ => “1-0″,
”1-1″ => “1-1″
},
”2″ =>
{
”2-0″ => “2-0″,
”2-1″ => “2-1″
}
};
该hash表可以这样来使用
$map->{”2″}->{”2-1″}
创建一个数组的数组(有时也可以叫“列表的列表”,不过不太准确)真是再简
单也不过了。它相当容易理解,并且本文中出现的每个例子都有可能在实际应用
中出现。
数组的数组就是一个普通的数组(@AoA),不过可以接受两个下标(“$AoA[3][2])。
下面先定义一个这样的数组: 一个包含有“指向数组的引用”的数组
@AoA = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
);
print $AoA[2][2];
bart
你可能已经注意到,外面的括号是圆括号,这是因为我们想要给数组赋值,所以
需要圆括号。如果你*不*希望这里是 @AoA,而是一个指向它的引用,那么就得
这样:
# 一个指向“包含有数组引用的数组”的引用
$ref_to_AoA = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "elroy", "judy", ],
];
print $ref_to_AoA->[2][2];
注意外面的括号现在变成了方括号,并且我们的访问语法也有所改变。这时因为
和 C 不同,在 Perl 中你不能自由地交换数组和引用(在 C 中,数组和指针在
很多地方可以互相代替使用)。$ref_to_AoA 是一个数组引用,而 @AoA 是一个
数组。同样地,$AoA[2] 也不是一个数组,而是一个数组引用。所以下面这
两行:
$AoA[2][2]
$ref_to_AoA->[2][2]
也可以用这两行来代替:
$AoA[2]->[2]
$ref_to_AoA->[2]->[2]
这是因为这里有两个相邻的括号(不管是方括号还是花括号),所以你可以随意
地省略箭头符号。但是如果 $ref_to_AoA 后面的那个箭头不能省略,因为省略
了就没法知道 $ref_to_AoA 到底是引用还是数组了 ^_^。
修改二维数组
前面的例子里我们创建了包含有固定数据的二维数组,但是如何往其中添加新元
素呢?再或者如何从零开始创建一个二维数组呢?
首先,让我们试着从一个文件中读取二维数组。首先我们演示如何一次性添加一
行。首先我们假设有这样一个文本文件:每一行代表了二维数组的行,而每一个
单词代表了二维数组的一个元素。下面的代码可以把它们储存到 @AoA:
while (<>) {
@tmp = split;
push @AoA, [ @tmp ];
}
你也可以用一个函数来一次读取一行:
for $i ( 1 .. 10 ) {
$AoA[$i] = [ somefunc($i) ];
}
或者也可以用一个临时变量来中转一下,这样看起来更清楚些:
for $i ( 1 .. 10 ) {
@tmp = somefunc($i);
$AoA[$i] = [ @tmp ];
}
注意方括号 ”[]“ 在这里非常重要。方括号实际上是数组引用的构造器。如果不
用方括号而直接写,那就犯了很严重的错误:
$AoA[$i] = @tmp;
你看,把一个数组赋值给了一个标量,那么其结果只是计算了 @tmp 数组的元素个
数,我想这肯定不是你希望的。
如果你打开了 ”use strict”,那么你就得先定义一些变量然后才能避免警告:
use strict;
my(@AoA, @tmp);
while (<>) {
@tmp = split;
push @AoA, [ @tmp ];
}
当然,你也可以不要临时变量:
while (<>) {
push @AoA, [ split ];
}
如果你知道想要放在什么地方的话,你也可以不要 push(),而是直接进行赋值:
my (@AoA, $i, $line);
for $i ( 0 .. 10 ) {
$line = <>;
$AoA[$i] = [ split ' ', $line ];
}
甚至是这样:
my (@AoA, $i);
for $i ( 0 .. 10 ) {
$AoA[$i] = [ split ' ', <> ];
}
你可能生怕 <> 在列表上下文会出差错,所以想要明确地声明要在标量上下文中
对 <> 求值,这样可读性会更好一些: (译者注:列表上下文中,<>
返回所有的行,标量上下文中 <> 只返回一行。)
my (@AoA, $i);
for $i ( 0 .. 10 ) {
$AoA[$i] = [ split ' ', scalar(<>) ];
}
如果你想用 $ref_to_AoA 这样的一个引用来代替数组,那你就得这么写:
while (<>) {
push @$ref_to_AoA, [ split ];
}
现在你已经知道如何添加新行了。那么如何添加新列呢?如果你正在做数学中的
矩阵运算,那么要完成类似的任务:
for $x (1 .. 10) {
for $y (1 .. 10) {
$AoA[$x][$y] = func($x, $y);
}
}
for $x ( 3, 7, 9 ) {
$AoA[$x][20] += func2($x);
}
想要访问的某个元素是不是存在是无关紧要的:因为如果不存在那么 Perl 会给
你自动创建!新创建的元素的值是 ”undef”。
如果你想添加到一行的末尾,你可以这么做:
# 添加新列到已存在的行
push @{ $AoA[0] }, ”wilma”, ”betty”;
注意我*没有*这么写:
push $AoA[0], ”wilma”, ”betty”; # 错误!
事实上,上面这句根本就没法通过编译!为什么?因为 push() 的第一个参数必
须是一个真实的数组,不能是引用。
访问和打印
现在是打印二维数组的时候了。那么怎么打印?很简单,如果你只想打印一个元
素,那么就这么来一下:
print $AoA[0][0];
如果你想打印整个数组,那你可不能这样:
print @AoA; # 错误!
因为你这么做只能得到一列引用,Perl 从来都不会自动地为你解引用。作为替
代,你必须得弄个循环或者是双重循环。用 shell 风格的 for() 语句就可以
打印整个二维数组:
for $aref ( @AoA ) {
print ”\t [ @$aref ],\n”;
}
如果你要用下标来遍历的话,你得这么做:
for $i ( 0 .. $#AoA ) {
print ”\t elt $i is [ @{$AoA[$i]} ],\n”;
}
或者这样用双重循环(注意内循环):
for $i ( 0 .. $#AoA ) {
for $j ( 0 .. $#{$AoA[$i]} ) {
print ”elt $i $j is $AoA[$i][$j]\n”;
}
}
如同你看到的一样,它有点儿复杂。这就是为什么有时候用临时变量能够看起来
更简单一些的原因:
for $i ( 0 .. $#AoA ) {
$aref = $AoA[$i];
for $j ( 0 .. $#{$aref} ) {
print ”elt $i $j is $AoA[$i][$j]\n”;
}
}
哦,好像还有点复杂,那么试试这样:
for $i ( 0 .. $#AoA ) {
$aref = $AoA[$i];
$n = @$aref - 1;
for $j ( 0 .. $n ) {
print ”elt $i $j is $AoA[$i][$j]\n”;
}
}
切片
切片是指数组的一部分。如果你想要得到多维数组的一个切片,那你得进行一些
下标运算。通过箭头可以方便地为单个元素解引用,但是访问切片就没有这么好
的事了。当然,我们可以通过循环来取切片。
我们先演示如何用循环来获取切片。我们假设 @AoA 变量的值和前面一样。
@part = ();
$x = 4;
for ($y = 7; $y < 13; $y++) {
push @part, $AoA[$x][$y];
}
这个循环其实可以用一个切片操作来代替:
@part = @{ $AoA[4] } [ 7..12 ];
不过这个看上去似乎略微有些复杂。
下面再教你如何才能得到一个 *二维切片*, 比如 $x 从 4 到 8,$y 从 7 到
12,应该怎么写?
@newAoA = ();
for ($startx = $x = 4; $x <= 8; $x++) {
for ($starty = $y = 7; $y <= 12; $y++) {
$newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
}
}
也可以省略掉中间的那层循环:
for ($x = 4; $x <= 8; $x++) {
push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
}
其实用 map 函数可以更加简练:
@newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;
虽然你的经理也许会抱怨这种难以理解的代码可能会带来安全隐患,
然而这种观点还是颇有争议的(兴许还可以更加安全也说不定 ^_^)。
换了是我,我会把它们放进一个函数中实现:
@newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
sub splice_2D {
my $lrr = shift; # 指向二维数组的引用
my ($x_lo, $x_hi,
$y_lo, $y_hi) = @_;
return map {
[ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
} $x_lo .. $x_hi;
}
语句@array = list;可以创建数组的引用,中括号可以创建匿名数组的引用。下面语句为用于画图的三维数组的例子:
$line = ['solid' , 'black' , ['1','2','3'] , ['4','5','6']];
此语句建立了一个含四个元素的三维数组,变量$line指向该数组。前两个元素是标量,存贮线条的类型和颜色,后两个元素是匿名数组的引用,存贮线条的起点和终点。访问其元素语法如下:
$arrayReference->[$index] single-dimensional array
$arrayReference->[$index1][$index2] two-dimensional array
$arrayReference->[$index1][$index2][$index3] three-dimensional array
可以创建在你的智力、设计经验和计算机的内存允许的情况下极尽复杂的结构,但最好对可能读到或管理你的代码的人友好一些–尽量使代码简单些。另一方面,如果你想向别人炫耀你的编程能力,Perl给你足够的机会和能力编写连自己都难免糊涂的代码。:)
建议:当你想使用多于三维的数组时,最好考虑使用其它数据结构来简化代码。
下面为创建和使用二维数组的例子:
1 #!/usr/bin/perl
2 #
3 # Using Multi-dimensional Array references
4 #
5 $line = ['solid', 'black', ['1','2','3'] , ['4', '5', '6']];
6 print “\$line->[0] = $line->[0] \n”;
7 print “\$line->[1] = $line->[1] \n”;
8 print “\$line->[2][0] = $line->[2][0] \n”;
9 print “\$line->[2][1] = $line->[2][1] \n”;
10 print “\$line->[2][2] = $line->[2][2] \n”;
11 print “\$line->[3][0] = $line->[3][0] \n”;
12 print “\$line->[3][1] = $line->[3][1] \n”;
13 print “\$line->[3][2] = $line->[3][2] \n”;
14 print “\n”; # The obligatory output beautifier.
结果输出如下:
$line->[0] = solid
$line->[1] = black
$line->[2][0] = 1
$line->[2][1] = 2
$line->[2][2] = 3
$line->[3][0] = 4
$line->[3][1] = 5
$line->[3][2] = 6
那么三维数组又如何呢?下面是上例略为改动的版本。
1 #!/usr/bin/perl
2 #
3 # Using Multi-dimensional Array references again
4 #
5 $line = ['solid', 'black', ['1','2','3', ['4', '5', '6']]];
6 print “\$line->[0] = $line->[0] \n”;
7 print “\$line->[1] = $line->[1] \n”;
8 print “\$line->[2][0] = $line->[2][0] \n”;
9 print “\$line->[2][1] = $line->[2][1] \n”;
10 print “\$line->[2][2] = $line->[2][2] \n”;
11 print “\$line->[2][3][0] = $line->[2][3][0] \n”;
12 print “\$line->[2][3][1] = $line->[2][3][1] \n”;
13 print “\$line->[2][3][2] = $line->[2][3][2] \n”;
14 print “\n”;
结果输出如下:
$line->[0] = solid
$line->[1] = black
$line->[2][0] = 1
$line->[2][1] = 2
$line->[2][2] = 3
$line->[2][3][0] = 4
$line->[2][3][1] = 5
$line->[2][3][2] = 6
访问第三层元素的方式形如$line->[2][3][0],类似于C语言中的Array_pointer[2][3][0]。本例中,下标均为数字,当然亦可用变量代替。用这种方法可以把数组和哈希表结合起来构成复杂的结构,如下:
1 #!/usr/bin/perl
2 #
3 # Using Multi-dimensional Array and Hash references
4 #
5 %cube = (
6 ’0′, ['0', '0', '0'],
7 ’1′, ['0', '0', '1'],
8 ’2′, ['0', '1', '0'],
9 ’3′, ['0', '1', '1'],
10 ’4′, ['1', '0', '0'],
11 ’5′, ['1', '0', '1'],
12 ’6′, ['1', '1', '0'],
13 ’7′, ['1', '1', '1']
14 );
15 $pointer = \%cube;
16 print “\n Da Cube \n”;
17 foreach $i (sort keys %$pointer) {
18 $list = $$pointer{$i};
19 $x = $list->[0];
20 $y = $list->[1];
21 $z = $list->[2];
22 printf ” Point $i = $x,$y,$z \n”;
23 }
结果输出如下:
Da Cube
Point 0 = 0,0,0
Point 1 = 0,0,1
Point 2 = 0,1,0
Point 3 = 0,1,1
Point 4 = 1,0,0
Point 5 = 1,0,1
Point 6 = 1,1,0
Point 7 = 1,1,1
这是一个定义立方体的例子。%cube中保存的是点号和坐标,坐标是个含三个数字的数组。变量$list获取坐标数组的引用:$list = $$ pointer{$i}; 然后访问各坐标值:$x = $list->[0]; … 也可用如下方法给$x、$y和$z赋值:($x,$y,$z) = @$list;
使用哈希表和数组时,用$和用->是类似的,对数组而言下面两个语句等效:
$$names[0] = “kamran”;
$names->[0] = “kamran”;
对哈希表而言下面两个语句等效:
$$lastnames{“kamran”} = “Husain”;
$lastnames->{“kamran”} = “Husain”;
Perl中的数组可以在运行中创建和扩展。当数组的引用第一次在等式左边出现时,该数组自动被创建,简单变量和多维数组也是一样。如下句,如果数组contours不存在,则被创建:
$contours[$x][$y][$z] = &xlate($mouseX, $mouseY);

Linode-很不错的VPS