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 = "fred@bedrock.edu"; # WRONG! Tries to interpolate @bedrock
$email = "fred\@bedrock.edu"; # Correct
$email = 'fred@bedrock.edu'; # 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左右 的記憶體空間,如果記憶體不夠,那就要小心了,如果記憶體夠,自然就沒有問題了。