Learning Perl 4ed Reading Notes - Chapter3 Lists and Arrays

1. List and Array. 本书中所讲的List是一种数据结构,Array是Perl中用来存放List的数据类型。大部分情况下,这两个单词可以互换,意思是一样的。和C不一样 的是,Perl的Array不用定义元素的类型,也就是说,Perl中的Array中的每个元素可以是Number,也可以是String,元素的类型没 必要都一样。和C相同的就是,对Array的访问都是通过下标进行的,第一个元素的数组下标是0。

2. Perl中的Array用起来和C中的没什么区别:

$fred[0] = "yabba";
$fred[1] = "dabba";
$fred[2] = "doo";
print $fred[0];
$fred[2] = "diddley";
$fred[1] .= "whatsis";

如果给定的下标不正确,那么将得到undef:

$blank = $fred[ 142_857 ]; # unused array element gives undef
$blanc = $mel; # unused scalar $mel also gives undef

3. Special Array Indices. Perl中的Array有一个比较有意思的事情就是,如果访问的数组下标越界了(超过数组大小了),那么Perl不会出错,Perl会自动为我们将数组扩容:

$rocks[0] = 'bedrock'; # One element...
$rocks[1] = 'slate'; # another...
$rocks[2] = 'lava'; # and another...
$rocks[3] = 'crushed rock'; # and another...
$rocks[99] = 'schist'; # now there are 95 undef elements

上面的例子中,rocks这个数组最后就变成有100个元素了,中间有95个undef的元素。

4. 我们可以用$#rocks来取得数组最后一个元素的index值,比如$#rocks就会得到99, 注意这个值实际比数组元素的个数要小1,因为第一个元素是从0开始的,$#rocks返回的是最后一个元素的index值,而不是数组大小
5. List Literals. 本节介绍用hard code的方式定义一个List。Perl定义一个List的方式非常灵活,如:

(1, 2, 3) # list of three values 1, 2, and 3
(1, 2, 3,) # the same three values (the trailing comma is ignored)
("fred", 4.5) # two values, "fred" and 4.5
( ) # empty list - zero elements
(1..100) # list of 100 integers

(1..5) # same as (1, 2, 3, 4, 5)
(1.7..5.7) # same thing - both values are truncated
(5..1) # empty list - .. only counts "uphill"
(0, 2..6, 10, 12) # same as (0, 2, 3, 4, 5, 6, 10, 12)
($m..$n) # range determined by current values of $m and $n
(0..$#rocks) # the indices of the rocks array from the previous section
("fred", "barney", "betty", "wilma", "dino")

从上面的例子可以看到,定义List的时候也可以使用变量,这样的话,一个List的值就不完全是静态的了,也可以是动态的。

此外,注意,使用..的时候,只能是uphill的,也就是说(5..1)这样是不行的。后面会介绍reverse operator,可以把List中的内容翻转过来。

6. "qw" shortcut. qw是一个shortcut,意思是"quoted words" or "quoted by whitespace",使用qw可以让我们快速的定义一个字符串List,使用qw的话,每个String可以不用引号,这样可以省略一些typing 的工作:

qw( fred barney betty wilma dino )
就等同于
("fred" "barney" "betty" "wilma" "dino")

而且非常不错的是,在Perl中用qw定义一个字符串List,delimiter可以自由选择:

qw! fred barney betty wilma dino !
qw# fred barney betty wilma dino # # like in a comment!
qw( fred barney betty wilma dino )
qw{ fred barney betty wilma dino }
qw[ fred barney betty wilma dino ]
qw< fred barney betty wilma dino >

这里可以看到,不一定要用()来把字符串括起来,完全可以用其他的字符,只要保证两个delimiter是一对符号或是一样的char就可以。

但是需要注意的是,用qw申明的List Literals,每个字符串相当于是用单引号括起来的,所以不能在字符串中使用变量,很多用backslash的转义也不能使用。此外,如果我们的字符串中要包含delimiter本身的话,请用backslash转义:

qw! yahoo\! google excite lycos ! # include yahoo! as an element

能够使用多种多样的字符作为delimiter是非常不错的,比如:

Code: Select all
qw{
  /usr/dict/words
  /home/rootbeer/.ispell_english
}


如上,我们要把一串UNIX的路径作为字符串,那么,就可以用非/的字符作为delimiter,如果Perl规定只能用/作为delimiter的话,那么上面的代码将会非常的不好读。
此外,为了方便,Perl还定义了一些特殊的数组下标,比如负值的数组index,最常用的就是-1,这表示数组的最后一个元素,所以下面两句代码效果是一样的:

$rocks[ -1 ] = 'hard rock';
$rocks[ $#rocks ] = 'hard rock';

-2, -3...依次类推,不过一般我们只会用一个-1,其他的实用意义就不大了。


7. List Assignment. 本节讲述如何给List赋值,注意这里讲的是List,不是Array。比如:

($fred, $barney, $dino) = ("flintstone", "rubble", undef);

这样就可以给三个变量赋值,这三个变量就可以理解成组成了一个List,于是,我们可以这样很方便的交换两个变量的值而不用通过一个中间变量:

($fred, $barney) = ($barney, $fred); # swap those values
($betty[0], $betty[1]) = ($betty[1], $betty[0]);

当右边赋值的清单比左边的变量多了或是少了,会怎么样呢?很简单,值多了会被ignore,变量多了,多出来的变量将全部被赋成undef:

($fred, $barney) = qw< flintstone rubble slate granite >; # two ignored items
($wilma, $dino) = qw[flintstone]; # $dino gets undef

注意上面的qw后面的<>,[]都是delimiter,没有什么其他的特别含义。

8. @ 符号。Perl提供了一个@符号,用来访问整个Array/List。比如:

@rocks = qw/ bedrock slate lava /;
@tiny = ( ); # the empty list
@giant = 1..1e5; # a list with 100,000 elements
@stuff = (@giant, undef, @giant); # a list with 200,001 elements
$dino = "granite";
@quarry = (@rocks, "crushed rock", @tiny, $dino);

如上,使用@就可以访问整个Array,非常方便。所以,用@copy = @quarry; 就表示将数组quarry中的所有元素赋给copy数组。

9. pop and push Operators. pop就是删除数组的最后一个元素并返回这个元素;push就是将一个元素加到数组的最后,数组长度加1。前面已经说了,用数组的index也可以做到这 一点(访问超过数组长度的index的时候Perl会自动为我们将数组扩展到这个大小,不会出错),为什么不用index,要专门发明pop和push 呢?很简单,第一,用push和pop更好理解;第二,用push和pop性能更高,因为在使用index的时候,特别是访问一个超过数组长度的 index的时候,Perl内部会触发异常,然后再处理这个异常,这中间做的事情很多,导致性能很低。所以,和C不一样,Perl中鼓励我们用push和 pop来操作数组末尾的元素。

@array = 5..9;
$fred = pop(@array); # $fred gets 9, @array now has (5, 6, 7,
$barney = pop @array; # $barney gets 8, @array now has (5, 6, 7)
pop @array; # @array now has (5, 6). (The 7 is discarded.)

push(@array, 0); # @array now has (5, 6, 0)
push @array, 8; # @array now has (5, 6, 0,
push @array, 1..10; # @array now has those 10 new elements
@others = qw/ 9 0 2 1 0 /;
push @array, @others; # @array now has those five new elements (19 total)

如果我们pop一个empty的array,那么将返回undef。

10. shift and unshift Operators. 和push、pop一样,不同的是shift/unshift操作的是数组开头的元素。

@array = qw# dino fred barney #;
$m = shift(@array); # $m gets "dino", @array now has ("fred", "barney")
$n = shift @array; # $n gets "fred", @array now has ("barney")
shift @array; # @array is now empty
$o = shift @array; # $o gets undef, @array is still empty
unshift(@array, 5); # @array now has the one-element list (5)
unshift @array, 4; # @array now has (4, 5)
@others = 1..3;
unshift @array, @others; # @array now has (1, 2, 3, 4, 5)

shift一个empty的array的时候,会返回undef。

11. Interpolating Arrays into Strings. 本节讲述在字符串中引用数组-当然是在双引号的字符串中。

@rocks = qw{ flintstone slate rubble };
print "quartz @rocks limestone\n"; # prints five rocks separated by spaces

很简单,用@符号就可以引用整个数组,用$array[index]引用数组中的一个元素。

这里插一句:再给list赋值的时候,默认都是用空格来分割各个元素,我们可以改变这一默认行为,和shell编程一样的,在Perl中,通过重定义$"就可以设定默认以什么字符为分隔符来区分list中的各个元素。

回过来,再引用整个数组的时候,打印出来的字符串前后都是没有空格的,所以,我们要手动在String中加入空格。

12. 前面看到了,我们用@符号引用了整个Array,那么,很自然会碰到的问题是,当我们的String是一个EMail地址的时候(含有@符号),那么,我 们要用backslash转义一下,否则Perl会认为我们在这个String中引用了一个数组,而不是一个EMail地址,如:

$email = "[email protected]"; # WRONG! Tries to interpolate @bedrock
$email = "fred\@bedrock.edu"; # Correct
$email = '[email protected]'; # Another way to do that

13. 综述。看例子:

@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"; # same thing

这里我们引用了数组中的一个元素,这里需要注意的是,$fred[$y-1]中,Perl 只会机械的将$y替换成2,如果这里的$y=2*4,那么Perl也只会机械的将$y替换成2*4,而不是8,然后如果我们打开了Warning的话,会 看到Perl把2*4解释成了2(因为Perl需要一个Number做-1的运算,按照我们前面学的,Perl在做自动类型转换)。千万注意。

@fred = qw(eating rocks is wrong);
$fred = "right"; # we are trying to say "this is right[3]"
print "this is $fred[3]\n"; # prints "wrong" using $fred[3]
print "this is ${fred}[3]\n"; # prints "right" (protected by braces)
print "this is $fred"."[3]\n"; # right again (different string)
print "this is $fred\[3]\n"; # right again (backslash hides it)

还有这里的例子,我们用{}强行显示定义了变量,从而将数组fred和变量fred分开了(Perl中数组和变量的名字是可以一样的,因为他们类型不同,但是我们强烈建议不要这样做,因为这样是在自找麻烦)。

 

14. foreach Control Structure. foreach可以循环遍历一个List,和shell编程中的循环有点类似。比如:

Code: Select all
foreach $rock (qw/ bedrock slate lava /) {
  print "One rock is $rock.\n";  # Prints names of three rocks
}


foreach 中有个非常重要的关键点:就是foreach中的control variable(上面例子中的$rock),不是List中变量的一个copy,而就是这个变量本身,换言之,在foreach中如果我们修改了 control variable,就会对List中的数据产生修改,千万注意。比如这个例子,就把List中每个元素都加上了\t和\n:

Code: Select all
@rocks = qw/ bedrock slate lava /;
foreach $rock (@rocks) {
  $rock = "\t$rock";       # put a tab in front of each element of @rocks
  $rock .= "\n";           # put a newline on the end of each
}
print "The rocks are:\n", @rocks; # Each one is indented, on its own line


15. 此外,foreach的control variable还有一个注意点,就是Perl会在foreach循环开始之前自动将control variable备份起来,当foreach循环结束后,Perl会自动将control variable的值恢复成foreach循环前的值,这一点和C都是不一样。换句话说,我们不用担心control variable的值在循环开始和结束后会有变化,但是如上面14点所描述的一样,在foreach循环体中对control variable的修改会直接影响List中的相应变量的值。

16. Perl's Favorite Default: $_ 。$_这个变量在Perl中很重要,也很常用,他是Perl中的默认变量--当我们的代码中没有指定变量的时候,$_就成了默认的变量。如:

Code: Select all
foreach (1..10) {  # Uses $_ by default
  print "I can count to $_!\n";
}


如上,在foreach中没有指定control variable,那么$_就成了control variable. 又如:

$_ = "Yabba dabba doo\n";
print; # prints $_ by default

由于print没有指定print什么变量,那么Perl就会打印$_变量的值。

17. reverse Operator. reverse用来反转一个List,有关reverse只需要记住一点:reverse不会修改List本身,reverse会把翻转后的结果作为返回 值传回,如果我们要修改List本身,那么只需要reverse的返回值再赋给List本身就可以了。

@fred = 6..10;
@barney = reverse(@fred); # gets 10, 9, 8, 7, 6
@wilma = reverse 6..10; # gets the same thing, without the other array
@fred = reverse @fred; # puts the result back into the original array

reverse @fred; # WRONG - doesn't change @fred
@fred = reverse @fred; # that's better

18. sort Operator. sort用来将一个List中的东西排序,默认情况下,sort是按照ASCII码表来排序的,那就是--大写字母排在小写字母前面,数字排在字母前面, 标点符号充斥在ASCII码表的各处。在第13章会描述如何自定义sort的排序逻辑。和reverse一样,sort不会修改List本身,sort是 把排序后的结果作为返回值返回,要想修改List本身,要把sort的返回值重新赋给List本身就可以了。

@rocks = qw/ bedrock slate rubble granite /;
@sorted = sort(@rocks); # gets bedrock, granite, rubble, slate
@back = reverse sort @rocks; # these go from slate to bedrock
@rocks = sort @rocks; # puts sorted result back into @rocks
@numbers = sort 97..102; # gets 100, 101, 102, 97, 98, 99

注意最后一个例子,排序的结果是100排在第一个哦,因为这是按照ASCII码表来排序的。

sort @rocks; # WRONG, doesn't modify @rocks
@rocks = sort @rocks; # Now the rock collection is in order
 

19. Scalar and List Context. 书上说本节是本章,甚至是本书最重要的一节,其实本节就是描述在不同的context上,scalar和list类型的变量的不同行为,就好像语言中一个 单词在不同的语境下会有不同意思的含义一样。这里的context指的就是变量所处的一个环境,一个expression。

20. 比如:

42 + something # The something must be a scalar
sort something # The something must be a list

@people = qw( fred barney betty );
@sorted = sort @people; # list context: barney, betty, fred
$number = 42 + @people; # scalar context: 42 + 3 gives 45

看这个例子,people是一个List,在最后一句代码中,我们将一个scalar变量和people相加,此时@people返回的是这个List中element的数量,而不是表示List本身,这就是同一个变量在不同的context下的不同表现。
一般来说,确定一个变量的真实表现,主要应该先看等号左边的变量类型是什么,这样就确定了等号右边expression应该给出一个什么类型的变量(是Scalar还是List),然后expression中的变量根据这样的要求给出不同的解释方法。这样的例子还有:

@list = @people; # a list of three people
$n = @people; # the number 3

21. 给出更多的一些例子和规则。比如,在一个List context中用sort,这是正常用法,在一个Scalar Context中用sort,sort会返回undef;在一个List context中用reverse,reverse将List的元素全部倒序重新排列,如果在Scalar Context中用reverse,reverse会返回一个倒序的String:

@backwards = reverse qw/ yabba dabba doo /; # gives doo, dabba, yabba
$backwards = reverse qw/ yabba dabba doo /; # gives oodabbadabbay

这里还有更多的例子:

$fred = something; # scalar context
@pebbles = something; # list context
($wilma, $betty) = something; # list context
($dino) = something; # still list context!

注意最后一个例子,最后一个例子等号左边是一个List,不是一个Scalar变量。

22. 再来看例子,这里的例子是把Scalar的变量赋给一个List类型的变量:

@fred = 6 * 7; # gets the one-element list (42)
@barney = "hello" . ' ' . "world";
@wilma = undef; # OOPS! Gets the one-element list (undef)
# which is not the same as this:
@betty = ( ); # A correct way to empty an array

23. Perl提供一个叫做scalar的关键字,用这个关键字可以手动强制指定一个变量为Scalar类型,如:

@rocks = qw( talc quartz jade obsidian );
print "How many rocks do you have?\n";
print "I have ", @rocks, " rocks!\n"; # WRONG, prints names of rocks
print "I have ", scalar @rocks, " rocks!\n"; # Correct, gives a number

在第三句代码中,print会把rocks这个array中的每个元素都打印出来,而我们想让print打印的是这个array的size,所 以,第四句代码中,我们用scalar关键字提示Perl,这里的@rocks作为Scalar变量来解释,所以就打印这个array的size了。

24. <STDIN> in List Context. 前面我们介绍了用<STDIN>可以读入一行(从文件或者是从keyboard读取),赋给一个Scalar变量;这里介绍 把<STDIN>用在List Context时候的情景--结果是,<STDIN>会一直读到end-of-file,然后把每行的string都作为List中的一个 element赋过去:

@lines = <STDIN>; # read standard input in list context

lines是一个List,所以,如果此时的<STDIN>是一个FILE的话,会一直读到文件末尾,然后每行作为一个 element赋给这个List;如果<STDIN>是keyboard的话,会把每行输入的string都作为一个element赋给这个 List,直到用户按下了ctrl+D(Linux/UNIX系统下,当然这个组合键可以通过stty命令来重定义,windows系统下一般是按下 ctrl+z表示EOF)。

这样每行读出来的string,末尾是有一个\n的,和前面一样,我们可以用chomp来去掉这个行尾的\n,不过有趣的是,chomp也可以用在List Context, 此时chomp会把List中每个element string行尾的\n去掉,很方便哦!

chomp(@lines = <STDIN>); # Read the lines, not the newlines

不过在使用<STDIN>的这个特性的时候,也要注意一些性能问题,比如一次性读入一个400MB的log file的时候会如何呢?和以往一样,Perl不会限制这个size的大小,只要memory够用,不过当我们真的干这样事情的时候,大概会用掉1G左右 的内存空间,如果内存不够,那就要小心了,如果内存够,自然就没有问题了。
 

你可能感兴趣的:(reading)