名称
关于Perl5中引用的简短教程
描述
关于Perl5中一个非常重要的新功能就是引用,我们可以用引用来完成操作复杂的数据结构,比如多为数组和嵌套哈希。但是这要求我们学习一些新的语法。废话少说,开始吧。
为什么Perl5里要加入引用?
Perl4里,你的哈希列表里的值只能是标量,不能是列表,也就是不能嵌套。懂?不懂看下面
下面有一张城市和其对应国家的文件
1 Chicago, USA 2 Frankfurt, Germany 3 Berlin, Germany 4 Washington, USA 5 Helsinki, Finland 6 New York, USA
然后你想读取这个文件,输出如下的格式,即把国家放在前面,然后是这个国家里城市名
1 Finland: Helsinki. 2 Germany: Berlin, Frankfurt. 3 USA: Chicago, New York, Washington.
一种办法就是你创建一个新的哈希,然后国家名就是这个新哈希的键,它的值就是与之相关的城市名所组成的列表。每次你从文件中读取一行数据,以逗号分割为城市名和国家名,然后判断你新创建的哈希里有没有这个国家名的键,没有就创建,然后把城市名加在与其所对应的值的列表里。大致过程就是这样了。
但是这是有前提的,就是哈希的值可以是一个列表,就好像你建了个数组,数组里的第一个元素又可以是另一个数组。但是在Perl4的时代这是不可能的。Perl4里如果你要完成类似的行为你可能会把多个城市名以逗号拼接成join一个字符串,然后把这个当成哈希的值存进去。
解决办法
当Perl5来临的时候,我们已经改变不了一些既定的事实了,那就是哈希里的值必须是标量scalar。所以解决问题的办法就是用引用。
引用也是标量,所以他没有违反以前的游戏规则。引用是什么,其实就像是人的名字,当你叫李三的时候,这里的李三就是李三这个人的引用。Perl里的引用都可以用来引用哪些类型的数据呢?可以引用数组,哈希。基本上是任何东西都可以被引用。
引用就像是你给其他东西取个了名字,比如你给一个数组或是哈希取了个名字叫jack,那么你就可以用jack来获取到这个数组或是哈希。现在来看看有关引用的语法。
语法
Perl提供了两种办法来创建引用,两种办法来使用引用。
创建引用
办法 1
在变量前添加反斜杠来获取该变量的引用
$aref = \@array; # $aref 现在就是 @array 的引用了, 以下同理 $href = \%hash; $sref = \$scalar;
一旦创建成功,那你就可以自由的创建和复制这些引用了,就像普通的标量一样。
$xy = $aref; # $xy 现在也是 @array 的引用 $p[3] = $href; # $p[3] 现在也是 %hash 的引用 $z = $p[3]; # $z 现在同样也是 %hash 的引用
这些例子都是通过已有的标量名来创建引用的,其实你可以直接为一组数据创建引用,节省一步,就像下面这样。
办法 2
$aref = [ 1, "foo", undef, 13 ]; # $aref 现在就是这个数组的引用了 如果没有这种语法,你就得这么来创建了 # @arr = [ 1, "foo", undef, 13 ]; $aref = @arr; $href = { APR => 4, AUG => 8 }; # $href 就是这个哈希的引用了
办法2和办法1创建出来的引用是一样一样的,只是办法2节省一次不必要的变量名创建:
# 这样: $aref = [ 1, 2, 3 ]; # 和这样是一样的: @array = (1, 2, 3); $aref = \@array;
如果你直接用[]或是{},那么你得到的就是一个全新的,空的数组或是哈希的引用了。
使用引用
现在你已经创建了引用,接下来就是在学习两种使用它的的方法。
使用方法 1
对于数组引用,你把引用名置于一对花括号里,然后就和普通的数组一样去操作就行了。比如用@{$aref}来代替@array来使用。
下面是例子:
数组:
1 @a; 2 @{$aref}; #同上一行的代码效果相同,$afref 是 @a 的引用,下面的同理 3 reverse @a; 4 reverse @{$aref}; #Reverse the array 5 $a[3]; 6 ${$aref}[3]; #An element of the array 7 $a[3] = 17; 8 ${$aref}[3] = 17; #Assigning an element
以上的代码,偶数行就是引用的用法啦。
关于哈希的用法,其实和数组是一样的:
1 %h; #$href是 %h 的引用 2 %{$href}; #A hash 3 keys %h; 4 keys %{$href}; #Get the keys from the hash 5 $h{'red'}; 6 ${$href}{'red'}; #An element of the hash 7 $h{'red'} = 17; 8 ${$href}{'red'} = 17; #Assigning an element
在循环里的用法也是一样的:
for my $element (@{$aref}) { ... }
循环打印引用哈希里的所有键值对:
for my $key (keys %{$href}) { print "$key => ${$href}{$key}\n"; }
使用方法 2
方法1已经够用了,但是你不觉得就是用起来有点麻烦吗,写出来的代码看起来很不美观啊,一堆花括号什么的。所以我们有方法2来拯救你。
${$aref}[3]; $aref->[3];#比上面这个美观多了吧 ${$href}{red}; $href->{red};#方法2一点也不2
一个例子
有如下的代码,定义了一个二维数组
@a = ( [1, 2, 3], [4, 5, 6], [7, 8, 9] );
那么$a[1]就是一个引用,它引用了一个一维数组,也就是[4,5,6]。记住下标从0开始。
那么$a[1]->[2]就是6。
箭头可选
$a[1]->[2] 看起来并不完美,其实可以把中间的箭头省略,变成这样$a[1][2] 是不是感觉好多了。
假设箭头并不是可选的,那么在某些情况就比较恶心了。比如你碰到了个二维以上的情况。
解决办法
关于前面那个一个国家包含多个城市名的问题,现在我们用引用来看看。
1 my %table; 2 while (<>) { 3 chomp; 4 my ($city, $country) = split /,/; 5 $table{$country} = [] unless exists $table{$country}; 6 push @{$table{$country}}, $city; 7 } 8 9 foreach $country (sort keys %table) { 10 print "$country: "; 11 my @cities = @{$table{$country}}; 12 print join ', ', sort @cities; 13 print ".\n"; 14 }
结尾
- 你可以对任何东西使用引用,包括标量,函数,或是其他引用(既可以创建引用的引用)
- 在使用方法1中,你其实是可以忽略那些花括号的,但前提是原本花括号要括住的东西就是标量。比如@$aref和@{$aref}是一样的,$$aref[1]和${$aref}[1]是一样的,但我们建议初学者还是不要装逼,老老实实加上花括号,或是使用方法2,即用箭头 ->
-
像下面这样做并不会真正的拷贝数组里的内容:
- $aref2 = $aref1;
你得到了两个指向相同数组的引用罢了. 如果你修改了
$aref1->[23],
然后你输出$aref2->[23]
看一看,你发现是一样的.如果你想要拷贝数组,请这么做
- $aref2 = [@{$aref1}];
这个其实用了[...]这个方法来创建了一个新的匿名数组,然后把这个新的数组的引用赋给了$aref2,新数组的内容就是拷贝来自用$aref1所指向的数组的数据。
同样的,拷贝哈希就是这样
- $href2 = {%{$href1}};
-
如果要判定一个变量里到底包不包含引用,可以用 ref 这个函数。这个函数的返回值为真,即代表有引用。实际上这个函数在检测到变量里包含哈希的时候就会返回HASH,包含数组的时候就会返回ARRAY,这两个返回值都是真。
-
如果你把引用当成是字符串来输出的话,就会得到如下信息
- ARRAY(0x80f5dec) or HASH(0x826afc0)
如果你看到输出的东西是这个,你就知道你不小心打印了一个引用。你可以使用 eq 来判断两个引用是否相等,但还是建议用 == ,因为这个要快多了。
-
你其实是可以把任意字符串来当成引用名来使用的,什么意思呢,看例子,其实也就是动态的引用名。这个叫软引用或是符号引用,这个是默认就开启的,要关闭的话就得在程序开头添加 use strrict 'refs'
1 $foo = 100; #$foo初始化为100 2 $name = "foo"; 3 $$name = 1; # $foo现在是1了
感谢
原文作者: Mark Jason Dominus