列表和数组
▲
1.列表指的是标量的有序集合,而数组则是存储列表的变量。更精确地说,列表指的是数据,而数组指的是变量。
2.数组或列表中的每个元素都有相应的整数作为索引,从0开始递增,每次加1。
3.数组或列表每个元素都是独立不相关的标量值,所以列表或数组可能包含数字、字符串、undef值或是不同类型标量值的混合。
4.列表的值不一定要放在数组里,但每个数组变量都一定包含一个列表(即便是不含任何元素的空列表)。
访问数组中的元素
▲
访问数组元素:
print $fred[0];
修改数组元素:
$fred[2] = "diddly";
▲
任何求值能得到数字的表达式都可以用作下标,假如它不是整数,则会自动舍去小数,无论正负。
$number = 2.71828; print $fred[$number - 1]; #结果和print $fred[1]相同
▲
假如下标超出数组的末端,则对应的值将会是undef。
特殊的数组索引($#rocks)
▲
如果对索引值超过数组尾端的元素进行赋值,数组将会根据需求自动扩大,增补元素默认取值为undef。
$rocks[0] = 'bedrock'; #一个元素 $rocks[1] = 'slate'; #又一个元素 $rocks[99] = 'schist'; #现在有97个undef元素
▲最后一个元素的索引值
对数组rocks而言,最后一个元素的索引值是$#rocks,但这个数字比数组元素的个数少1,因为还有一个编号为0的元素。
$end = $#rocks; #99,也就是最后一个元素的索引值 $number_of_rocks = $end + 1; #100,数组元素的个数 print $rocks[$#rocks]; #访问最后一个元素
▲负数数组索引
注意:超出数组大小的负数索引值只会得到undef,而不会绕回到数组尾部。
print $rocks[-1]; #和$rocks[$#rocks]一样,但更简单 print $rocks[-100]; #得到'bedrock' $rocks[-200] = 'crystal'; #致命错误!
列表直接量
▲
列表直接量(也就是在程序代码中表示一列数据的写法),可以由小括号内用逗号隔开的一串数据表示,这些数据就称为列表元素。
(1,2,3) #包含数字1、2、3这三个数字的列表 (1,2,3,) #包含数字1、2、3(末尾的逗号会被忽略) ("fred",4.5) #两个元素,"fred"和4.5 () #空列表——零个元素
▲范围操作符(..)
如果是某种规律的序列,不用逐个键入。范围操作符(..)可用于自动创建它两侧标量值之间的所有值。
(1..100) #从1到100的整数序列 (1..5) #等同于(1,2,3,4,5) (1.7..5.7) #同上,等同于(1,2,3,4,5),小数点部分去掉后取范围 (0,2..6,10,12) #等同于(0,2,3,4,5,6,10,12)
NOTE:
1.范围操作符只能从小到大依次累加
(5..1) #得到的是空列表
2.列表中的元素可以不必都是常数,它们可以是表达式。
($m,17) ($m+$o, $p+$q) ($m..$n) (0..$#rocks) #rocks数组的下标序列
▲qw简写
qw简写可以快速输入,免除反复键入引号和逗号。
("fred","barney","betty","wilma","dino") 等同于 qw( fred barney betty wilma dino)
NOTE:
1.在qw构建的列表中,Perl都会将其当成单引号内的字符串来处理,所以,不能像双引号内的字符串一样使用 或$fred。其中的空白字符(如空格、制表符以及换行符)会被抛弃,然后剩下的就是列表中的元素。
所以,上面列表可以写成这样:
qw( fred
barney betty
wilma dino)
2.因为qw算是一种引用的形式,所以不能将注释放在qw列表中。
qw( fred #abc,在这里加注释会被当成一个元素 barney betty wilma dino )
3.除了圆括号(),Perl还允许用任何标点符号作为定界符,也可以是成对符号。
qw!fred barney betty wilma dino! qw/fred barney betty wilma dino/ qw#fred barney betty wilma dino# qw{fred barney betty wilma dino} qw[fred barney betty wilma dino] qw<fred barney betty wilma dino>
注意:
(1)如果需要在被圈引的字符串内使用定界符,可通过反斜线转义来引入这个字符。
qw! yahoo! google ask msn ! #将yahoo!作为一个元素引入
(2)和单引号内的字符串一样,两个连续的反斜线表示一个实际的反斜线。
qw( This is a \ real backslash);
列表的赋值
▲列表值赋值到变量
($fred, $barney, $dino) = ("flintstone", "rubble", undef);
NOTE:
1.如果变量个数>列表值的个数,多出来的值会被忽略掉
($fred, $barney) = qw< flintstone rubble slate granite > #忽略掉末尾的两个元素
2.如果变量个数<列表值的个数,多出来的变量会被设成undef(或空列表,后面解释)
($wilma, $dino) = qw[flinstone]; #$dino的值为undef
▲交换两个变量的值
($fred, $barney) = ($barney, $fred); ($betty[0], $betty[1]) = ($betty[1], $betty[0]);
▲构建一个字符串数组
($rock[0], $rock[1], $rock[2], $rock[3]) = qw/talc mica feldspar quartz/;
▲引用整个数组(用@字符)
在赋值操作符两边都可以用
@rock = qw/bedrock slate lava/; @tiny = (); #空列表 @giant = 1..1e5; #包含100000个元素的列表 @stuff = (@giant, undef, @giant); #包含200001个元素的列表
注意:
1.
$dino = "granite"; @quarry = (@rock, "crushed rock", @tiny, $dino);
这个赋值运算将会把@quarry的值设成拥有5个元素的列表(bedrock, slate, lava, crushed rock, granite),因为@tiny贡献了0个元素给这个列表(注意,由于空列表里没有任何元素,也就不会有undef被赋值到列表中,如果需要undef,也可以显式写明)。
2.
列表中的数组名会被展开成(它所拥有的)元素列表,因为数组只能包含标量,不能包含其他数组,所以数组无法成为列表中的元素。
3.
就像标量变量的初始值是undef一样,新的或是空的数组的初始值是空列表,即()。
pop和push操作符
▲pop操作符:提取数组末尾的元素并将其作为返回值
@array = 5..9; $fred = pop(@array); # $fred为9,@array现在是(5,6,7,8) $barney = pop @array; # $barney为8,@array现在是(5,6,7) pop @array; # @array现在是(5,6),pop的返回值7被抛弃了
NOTE:
1.最后一行是在“空上下文”中使用pop操作符。所谓空上下文,只不过是表示返回值无处可去的一种说辞。这其实也是pop操作符常见的一种用法,用来删除数组中最后一个元素。
2.如果数组是空的,pop什么也不做,直接返回undef。
3.pop后面加不加括号都可以。
▲push操作符:添加一个(或一串)元素到数组尾端
@array = (5,6); push (@array, 0); # @array现在是(5,6,0) push @array, 8; # @array现在是(5,6,0,8) push @array, 1..10 # @array得到了10个新元素 @others = qw/5 2 0 y f b/; push @array, @others; # @array又得到了6个新元素(共20个)
注意:
push或pop的第一个参数都必须是要操作的数组变量,对列表直接量进行压入(push)或弹出(pop)操作是没有意义的。(因为对列表直接量进行操作后,处理后的列表又怎么表示呢?)
shift和unshift操作符
▲shift操作符:提取数组开头的元素并将其作为返回值
@array = qw#dino fred barney#; $m = shift(@array); # $m变成"dino",@array现在是("fred", "barney") $n = shift @arry; # $n变成"fred",@array现在是("barney") shift @array; # 现在@array变空了 $o = shift @arry; # $o变成undef,@array还是空的
▲unshift操作符:添加一个(或一串)元素到数组开头
unshift(@array, 5); # @array现在仅包含只有一个元素的列表(5) unshift @array, 4; # @array现在是(4,5) @others = 1..3; unshift @array, @others; # @array变成了(1,2,3,4,5)
splice操作符
push-pop和shift-unshift操作符都是针对数组首尾的元素操作的,splice操作符是针对数组中间的元素操作的。
splice含义:删除操作。
▲splice可接受4个参数,后两个参数可选
splice(参数1,参数2,参数3,参数4)
参数1:要操作的数组
参数2:要操作的一组元素的开始位置(索引值)
参数3(可选):指定要操作的元素长度(即元素个数)
参数4(可选):要替换的列表
▲接受两个参数:
@array = qw(pebbles dino fred barney betty); @removed = splice @array, 2; #在原来的数组中删掉从fred(索引值为2)开始往后的元素 #@removed变成qw(fred barney betty) #原先的@array变成qw(pebbles dino)
NOTE:
1.splice返回值是什么? ——删除的元素列表
2.是"原地更新"吗? ——是
▲接受三个参数
第3个参数作用是指定要操作的元素长度(注意不是结束位置),即元素个数,通过这个参数,可以删除数组中间的一个片段。
@array = qw(pebbles dino fred barney betty); @remoed = splice @array, 1, 2; #删除dino和fred两个元素 # @remoed变成qw(dino fred) # @array变成qw(pebbles barney betty)
▲接受四个参数
第4个参数是要替换的列表,也就是可以补充新元素到原来的数组中,新加入列表的长度不一定要和拿走的元素片段长度一样。
@array = qw(pebbles dino fred barney betty); @remoed = splice @array, 1, 2, qw(wilma); #删除dino和fred # @remoed变成qw(dino fred) # @array变成qw(pebbles wilma barney betty)
▲特殊用法
实际上,添加元素列表并不需要预先删除某些元素,把表示长度的第三个参数设为0,既可不加删除地插入新列表。
@array = qw(pebbles dino fred barney betty); @remoed = splice @array 1, 0, qw(wilma); #什么元素都不删除 # @remoed变成() # @array变成qw(pebbles wilma dino fred barney betty)
注意:
wilma出现在dino 之前的位置上,Perl从索引值1的地方插入新列表,然后顺移原来的元素。
字符串中的数组内插
▲
和标量一样,数组的内容同样可以被内插到双引号引起的字符串中。内插时,会在数组的各个元素之间自动添加分隔用的空格。
@rocks = qw( flinstone slate rubble); print "quartz @rocks limestone "; #打印5个以空格隔开的单词
NOTE:
1.数组被内插后,首尾都不会增添额外空格,若真的需要,可以自己手动添加
2.如果@为双引号内字符串的内容本身时,需引入反斜线转义@或直接用单引号来定义字符串
$email = "fred@bedrock.edu"; #错!这样会内插@bedrock这个数组 $email = "fred@bedrock.edu"; #正确 $email = 'fred@bedrock.edu'; #另一种写法,效果相同
3.内插数组中的某个元素时,会被替换成该元素的值。
@fred = qw(hello dolly); $y = 2; $x = "This is $fred[1]'s place"; #得到This is dolly's place $x = "This is $fred[$y-1]'s place"; #效果同上
4.如果要在某个标量变量后面紧接着写左方括号,需要隔离,否则会被识别为数组引用。
@fred = qw(eating rocks is wrong); $fred = 'right'; print "this is $fred[3] "; #用到了$fred[3],打印"wrong" print "this is ${fred}[3] "; #打印right[3](用花括号避开歧义) print "this is ${fred}"."[3] "; #打印right[3](用分开的字符串) print "this is $fred[3] "; #打印right[3](用反斜线避开歧义)
foreach控制结构
▲foreach循环能逐项遍历列表中的值,依次提取使用:
foreach $rock(qw/ bedrock slate lava /){ print "One rock is $rock. "; #依次打印这三个单词 }
NOTE:
1.每次循环迭代时,控制变量$rock都会从列表中取得新值。
2.控制变量并不是列表元素的复制品,实际上,它就是列表元素本身。也就是说,假如在循环体中修改了控制变量的值,也就同时修改了列表元素。
@rocks = qw/ bedrock slate lava /; foreach $rock(@rocks){ $rock = " $rock"; #在@rocks的每个元素前加上制表符 $rock .= " "; #同时在末尾加上换行符 } print "The rocks are: ",@rocks; #各自占一行,并使用缩排(注意这种写法,从第二个列表元素开始前面都带一个空格)
注意:
@rocks = qw(a b c d); print @rocks; #会得到abcd print "@rocks"; #会得到a b c d
▲
循环结束后,控制变量的值仍然是循环执行之前的值。Perl会自动存储foreach循环的控制变量并在循环结束后还原。
在循环执行期间,我们无法访问或改变已存储的值,所以当循环结束时,变量仍让保持循环前的值。
如果它之前从未被赋值,那就仍然是undef。
也就是说,如果想把循环的控制变量取名为$rock的话,不必担心之前是否用过同名的变量。
$rock = 'shale'; @rocks = qw/ bedrock slate lava /; foreach $rock(@rocks){ ... } print "rock is still $rock "; #打印'rock is still shale'
Perl的默认变量:$_
▲
假如在foreach循环开头省略控制变量,Perl就会用它最喜欢用的默认变量$_。
这个变量名除了名称比较特别以外,和其它标量变量几乎没什么差别。
foreach (1..10){ #默认会用$_作为控制变量 print "I can count to $_! "; }
▲
Perl有许多默认变量,$_是最常用的一个,如果不加说明,Perl默认使用$_。
$_ = "Yabba dabba doo "; print; #默认打印$_
reverse操作符
▲
reverse操作符会读取列表的值(一般来自数组),并按相反次序返回新的列表。
@fred = 6..10; @barney = reverse(@fred); #返回10,9,8,7,6 @wilma = reverse 6..10; #同上,但无需额外的数组 @fred = reverse @fred; #倒序后保存到原来的数组
注意:
reverse只是返回倒序后的列表,它不会修改给它的参数。加入返回值无处可去,那这种操作也就变得毫无意义。
reverse @fred; #错误的用法,这不会使数组内容倒序 @fred = reverse @fred; #正确用法
sort操作符
▲
sort操作符会读取列表的值(一般来自数组),依次按字符的内部编码顺序对它们排序。
排序按照ASCII码的大小进行。
@rocks = qw/ bedrock slate rubble granite /; @sorted = sort(@rocks); #返回bedrock,granite,rubble,slate @back = reverse sort @rocks; #逆序,从 slate 到 bedrock 排列 @rocks = sort @rocks; #将排序后的结果存到原数组@rocks @numbers = sort 97..102; #得到100,101,102,97,98,99
NOTE:
1.按默认排序规则,任何以1开头的字符串会被排在以9开头的字符串之前。
2.排序操作和reverse操作一样,不会修改原始参数,只是返回新的列表。所以要对数组排序,就必须将排序后的结果存回数组。
each操作符
从Perl 5.12开始,已经可以针对数组使用each操作符了。在这之前each只能用于提取哈希的键-值对。
▲
每次对数组调用each,会返回数组中下一个元素对应的两个值——数组索引与元素值:
require v5.12; @rocks = qw/ bedrock slate rubble granite /; while (($index, $value) = each @rocks){ print "$index: $value "; }
注意:
这里使用了require,因为使用use v5.12会默认启用“严格(strict)”模式。
▲
如果不用each,就得根据索引从小到大遍历,然后借助索引取得元素值。
@rocks = qw/ bedrock slate rubble granite /; foreach $index(0..$#rocks){ print "$index: $rocks[$index] "; }
标量上下文与列表上下文
▲
同一个表达式出现在不同的地方时会有不同的意义。
所谓上下文,指的是如何使用表达式。
前文接触过很多针对数字和字符串的上下文操作,比如按照数字进行操作时得到的就是数字结果,按照字符串进行操作时返回的则是字符串结果。而且,起到决定性因素的是操作符,而不是被操作的各种变量或直接量。
2*3 #得到数字6 2x3 #得到字符串222
▲
当Perl在解析表达式时,要么希望它返回一个标量,要么希望它返回一个列表。
表达式所在的位置,Perl期望得到什么,那就是该表达式的上下文。
42 + something #这里的something必须是标量 sort something #这里的something必须是列表
▲
就算是同一个表达式名称,有时返回的是单个标量值,有时返回的却是列表,这取决于上下文。
在Perl里,表达式总是按照需要的上下文返回对应的值。
NOTE:
1.以数组的“名称”为例,在列表上下文中返回元素列表,在标量上下文中则返回数组的元素个数。
@people = qw(fred barney betty); @sorted = sort @people; #列表上下文,得到 barney,betty,fred $number = 42 + @people; #标量上下文:42 + 3 得45
2.即使是普通的赋值操作符(对标量或列表赋值),也可以有不同的上下文。
@list = @people; #得到3个人的姓名列表 $n = @people; #得到人数3
3.不要立刻得出结论,认为列表在标量上下文中一定会得到元素的个数而非元素列表。许多能返回列表的表达式会有各种丰富有趣的行为。
4.其实“产生列表”的表达式和“产生标量”的表达式之间并无不同,任何表达式都可以产生列表或标量,根据上下文而定。
在标量上下文中使用产生列表的表达式
▲sort在标量上下文中总是返回undef
@sorted = sort qw/ fred barney betty /; #返回barney, betty, fred $sorted = sort qw/ fred barney betty /; #返回undef
▲reverse在标量上下文中返回逆序后的字符串(先将列表中所有的字符串全部连接在一起,再对结果中的每一个字符作逆序处理)
@sorted = reverse qw/ fred barney betty /; #返回betty, barney, fred
$sorted = reverse qw/ fred barney betty /; #返回yttebyenrabderf
▲
一个典型的例子:
@array = qw( talc quartz jade ); if (@array){ #标量上下文中@array返回元素的个数3,表示真 print "Hello"; }
在列表上下文中使用产生标量的表达式
▲
在列表上下文中,如果表达式求值结果为标量值,则自动产生一个仅含此标量值的列表。
@fred = 6 * 7; #得到仅有单个元素的列表(42) @barney = "Hello".' '."world"; #得到仅有单个元素的列表('Hello world')
▲
由于undef是标量值,所以把undef赋值给数组并不会清空数组,得到的还是一个数组,内含一个空值元素。
@wilma = undef; #得到的是单个元素的列表,元素值为未定义的(undef)
要清空数组,直接赋予一个空列表:
@wilma = ( ); #这才是清空数组的正确方法
强制指定标量上下文
▲
在列表上下文中强制引入标量上下文,使用伪函数scalar。
它不是真正的函数,只是用于告诉Perl这里要切换到标量上下文。
@rocks = qw( talc quartz jade obsidian); print "I have ", @rocks, " rocks! "; #得到I have talcquartzjadeobsidian rocks! print "I have ", scalar @rocks, " rocks! "; #得到I have 4 rocks!
列表上下文中的<STDIN>
▲
在标量上下文中,<STDIN>返回的是输入数据的下一行内容;
在列表上下文中,返回的则是所有剩下行的内容,一行一个元素,直到文件结尾。
▲
如果输入数据来自键盘,如何发送结尾标记?
Unix或类似系统:Ctrl+D
DOS/Windows:Ctrl+Z
▲
输入完成后,每个元素都是一个以换行符结尾的字符串,因为这些换行符也是输入的内容。
chomp可以一次性去掉每个元素的换行符:
写法一:
@lines = <STDIN>; chomp @lines;
写法二:
chomp(@lines = <STDIN>);
▲
如果文件太大,行输入操作符会读取所有行并占用大量内存,所以这时应该选用更合理的方式。