詳談變數
請見 範例 並參考 perldata(1)。 也請先了解有關 l-value 與 r-value 的觀念。
List
凡是用逗點分開的一串純量, 就叫做一個 list。 整個 list
外面可以包上一對小括弧; 也可以省略。 它不是另外一種新的資料形態
(沒有一種變數叫做 list), 只是用手寫出來的資料。 list 可以說是 array
版的 literal。 凡是手冊上提到可以使用 list 的地方,
其實都可以放陣列。 例如 sort LIST 可以對一串純量 (或一個陣列)
排序, 傳回排序的結果; 又如 reverse LIST 可以把一串純量 (或一個陣列)
顛倒過來。 這類函數只要求他們的參數扮演 r-value 的角色,
運算結果以傳回值的方式留下, 並沒有去修改參數本身。 所以如果
@data = (1, 2, 3);
則 @x = (@data, 4, 5, (reverse
@data))
會將 @x 修改成 (1, 2, 3, 4, 5, 3, 2, 1)
;
但是 @data 還是 (1,2,3)。
反過來說, 可以用陣列的地方, 是否一定也可以用 list 呢? 未必見得。 有些函數要求參數既要扮演 r-value 又要扮演 l-value 的角色, 這種情況手冊就會寫清楚只能用陣列, 例如 pop 函數。 用大家熟悉的觀念作一個比較:
$x = 5; | --$x; | --5; |
$x = "xyz\n"; | chomp $x; | chomp "xyz\n"; |
@x = (7,3); | pop @x; | pop (7,3); |
上例中 pop (7,3);
是錯誤的, 錯誤的原因和
--5;
與 chomp "xyz\n";
一樣。 Literal
只能當做 r-value (提供值) 而不能當做 l-value (提供空間來裝值); 但是
--, chomp, pop 等等 operators/functions 要求其 operands/arguments
扮演 l-value 的角色。 用白話文來說, 這些 operators/functions
具有破壞性 (destructive), 必須把算完的結果存入一個空間, 而 literals
無法提供一個可以裝值的空間。
List 沒有層次: 一個 list 外面不論加了多少對小括弧
(或是完全沒有小括弧), 都還是同樣的東西; 兩個 lists 併在一起寫,
會構成一個更長的 list, 而不是一個多層次的 list。 也就是說 @data
= ((("Slow"), "and", ("sure")), "wins", ("the", "race"));
和
@data = ("Slow", "and", "sure", "wins", "the", "race")
是一樣的。
所以 perlfunc(1) 當中解釋每個函數語法的地方, "LIST"
其實可以不只是一個參數, 而是「一串由逗點分開的許多純量及陣列」。
這些內建函數需要 LIST 的地方, 小括弧可有可無。 例如 printf "%d
+ %d = %d", 2 , 3, 5;
和 printf("%d + %d = %d", 2, 3,
5);
效果一樣。 當然, 語法上不該逗點的地方,
使用時就不可以多給逗點, 否則會被 perl 誤以為是後面 LIST 的一部分,
例如 printf STDOUT, "%d + %d = %d", 2, 3, 5;
一句當中,
STDOUT 後面誤加的逗點, 會讓 perl 誤以為 STDOUT 是 format 字串 (而不是
file handle)。
最簡單的 array 與 hash 初始值設定的語法, 右邊就是一個 list。
在一個 list 當中, => 和逗點的效果一樣 (小小差別: => 會自動
quote 左邊的字串)。 所以 @weekdays = ("Mon" => "Tue", "Wed"
=> "Thu");
和 %days = ("Jan", 31, "Feb", 28);
雖然看起來很奇怪, 但都是合法的句子。 Q: %h = reverse @a;
這句話有什麼效果呢?
range operator: @t = 5..8;
是 @t = (5, 6, 7,
8)
的簡寫。
repetition operator: @t = (9,8,7,6) x 3;
會使
@t 變成 (9,8,7,6,9,8,7,6,9,8,7,6)
利用 list 語法, 可以把一串元素拷貝給一串元素。 Perl 會按對應順序拷貝, 各元素對號入座。 但你可以想像所有元素的舊值會被事先備份出來, 所以不會有孰先孰後, 舊值不見的問題。 例如要交換兩個元素可以用: ($x, $y) = ($y, $x); 又, 左邊 list 當中, 第一個出現的 array 變數, 會把右邊剩下的所有值 "吃掉", 所以通常左邊最多只有一個 array 變數, 且通常都出現在最後。 例如 ($x, $y, @z) = (@p, @q); 的效果相當於 $x=$p[0]; $y=$p[1]; @z = (@p[2..$#p], @q);
用 regexp 搜尋一整批字串時, 先前介紹的 $1, $2, ... 語法不太美觀:
$str =~ m#(\w+)/(\d+)/(\d+);
$mon = $1;
$day = $2;
$year = $3;
變數沒有名字, 程式不易閱讀; 且 $1, $2, ... 只能保留到下次再用
regexp 之前, 所以要趕快用有名字的變數把它們記起來。 現在既然學了
list, 就可以用一個比較簡潔的語法, 直接把比對結果指定給一個 list,
像這樣: ($mon, $day, $year) = $str =~
m#(\w+)/(\d+)/(\d+)#;
甚至可以把比對結果指定給一個陣列,
這樣就可以在不知道會比對到幾個 「我有興趣的字串」 的情況下,
取得所有字串。 這種用法通常與 {m,n} 計數樣版或 g 選項配合使用, 例如:
perl -ne 'print "$.: ",join(",",@x),"\n" if (@x = m/(\d+)/g)'
檔名
注意 (1) 這裡的 = 並沒有打錯。 那麼比對字串用的 =~
跑到那裡去了呢? 其實是連同 $_ 一起省略掉了。 (2) 這句 if 同時有
「查詢是否比對到」 及 「記下比對到的字串」 雙重效果, 也是常用句型。
Array slice (陣列切片?)
假設有 @data 這個 array , 則 ($data[3], $data[0],
$data[4])
可以簡寫為 @data[3, 0, 4]
語法解釋:
我要從名叫 data 的變數中拿元素出來, 所以用 ... data ... ; 因為 data
是一個 array, 所以後面用中括弧; 而拿出來的結果是好幾個元素, 構成一個
array, 所以前面用 @。
假設有 %data 這個 hash , 則 ($data{"Feb"}, $data{"May"},
$data{"Nov")
可以簡寫為 @data{"Feb", "May",
"Nov"}
解釋: 我要從名叫 data 的變數中拿元素出來, 所以用 ...
data ... ; 因為 data 是一個 hash, 所以後面用大括弧;
而拿出來的結果是好幾個元素, 構成一個 array, 所以前面用 @
Q: 以下各運算式之間有什麼關係? $x, @x, %x, $x[3], $x{"3"}, @x[0, 3], @x{"0", "3"}
Q: 假設有 %x = (Jan=>12, Feb=>-5, ..., Nov=>8, Dec=>37); 請用 array slice 的語法及 list 對拷的語法, 一句話 同時將 Jan 與 Dec 所對應的值對調, 並將 Feb 與 Nov 所對應的值對調。 答案在網頁原始碼當中。
Q: 我們想將 @x 的每個元素往前移兩位, 而最前面兩個元素則繞到最後面去。 請利用 array sliace 語法一句話就完成。
變數有沒有值? hash 有沒有這個 key?
在 perl 裡面, 可以用 if ($x)
去詢問某變數的值, 如果
$x 裡面是 0 或空字串 "" 或空的 list (), 那麼答案都是 false;
其他數字或字串都是 true。 查詢 if (@x)
時,
如果 @x 是空的 list, 或是裡面所有元素都是 0 或空字串,
那麼答案也是 false。
如果變數根本就沒有設定過初始值, 那麼 perl 就會稱它裡面含的是
undef, 意思就是沒有定義 (undefined)。 下 perl -we 'print
$x+$x,"\n"'
所看到的錯誤訊息 "Use of uninitialized value ..."
指的就是這種狀況。 如何判斷一個變數 $x 裡面有沒有值呢? 可以用
if (defined($x))
另外,
有時就是想把已有值的變數的內容完全清除乾淨 (不想存 0, 也不想存空字串,
什麼值都不想存), 這時可以下 undef($x);
或 $x =
undef;
一個 hash %x 裡面的元素, 我們除了可以對它問以上的問題 ("它的值是
true 還是 false?" if ($x{"dog"})
或
"它是不是連個值都沒有?" if (defined($x{"dog"}))
)
還可以問另外一個問題: "hash 裡面有這個 key 嗎?" if
(exists($x{"dog"}))
如果連 key 都不存在, 就不必談有沒有傎,
更不必談它的值是 true 還是 false 了。 另外, 如果就是想把既有的 key
刪掉 (當然也就連同它的 value 一起刪掉) 可以用
delete($x{"dog"});
例: ++$freq{"this"}
第一次執行時, 歷經三種狀態:
一開始 exists $freq{"this"}
為 false; 然後 exists
$freq{"this"}
為 true 而 defined $freq{"this"}
為
false; 最後兩者皆為 true。
Reference
用 "\" 可取得一個變數的位址。 例: perl -e 'print \$_,
"\n"'
又例: perl -e 'print \@ARGV, "\n"'
又例:
perl -e 'print \%ENV, "\n"'
不論是純量, 陣列或是 hash, 任何一個變數的位址只佔一個純量的大小。 把某個變數 (可以是純量, 陣列或是 hash) 的位址存到純量 $x 去, $x 就成了一個 reference variable。 也不是字串, 而是另外一個變數的位址。
reference 變數的內容不是字串也不是數字, 但一樣可以印出來。 Perl 會告訴你這個變數 "指到" 記憶體的什麼地方去, 還有被指到的這個地方存放的是什麼樣的變數 (純量, 陣列或是 hash)。
熟悉 c/c++ 的讀者請注意: perl 的 reference variable 相當於 c/c++ 當中的 pointer variable; 與 c++ 當中的 reference variable 不同。
深入探討 reference 之前, 再次提醒: 看到 $x 有兩種可能的解釋: 要取值出來, 或是要裝東西進去; 看到 \$x 則一律是要取位址。
簡單 reference 想法: 若 $abc = \$xyz, 則以下每當 $abc 出現時, 都把它想成是 xyz。 參考到 array 或 hash 的 reference 亦同。 差不多可以說 \ 和 $ 互相抵消, \ 和 @ 互相抵消, \ 和 % 互相抵消。 例如 $abc = \@xyz; 則 @xyz 陣列最後一個元素的註標可用 $#$abc 取得, 而 @xyz 的最前面元素可以用 $$abc[0] 取得。 如果沒有把握, 就寫 ${$abc}[0]
分析/表達複雜的 reference 時, 用大括號 { ... } 而不是用小括號 ( ... ) 來表示運算順序的先後。
"箭頭表示法": "... 所指到的 array 內的 ... 這個元素" 可用 ...->[...] 表示; "... 所指到的 hash 內的 ... 這個元素" 可用 ...->{...} 表示。 ("一次跳兩步" 的思考方式)
(無名陣列) 用方括號可直接產生一個 anonymous array, 這個 array 的 reference 就放在 [ ... ] 出現的地方; (無名 hash) 用大括號可直接產生一個 anonymous hash, 這個 hash 的 reference 就放在 { ... } 出現的地方 。 例如右圖可由下句產生: $x = { "Mon"=>[1,2,3,4], "Tue"=>[5,6], "Wed"=>[7,8,9] };
Q: 下圖中紅色框圈起來的四塊, 分別要如何稱呼? (請寫 perl 算式) 答案在網頁原始碼當中。
複雜的資料結構可用層層疊疊的無名 array 或 hash 來表示。 一連串的 -> 可以只寫第一個, 其餘全部省略。 請參考 sched 範例。
perllol(1) 內有很多例子; 也請參考 perlref(1)。 遇到複製的變數, 一層又一層的 anonymous arrays 與 anonymous hashes, 建議務必畫圖!
複製 reference 變數時要注意: 如果沒有仔細思考, 可能會變成 shallow copy, 產生出 "雙頭怪"。 需要完全複製出一份獨立的資料 (稱為 deep copy, "深層拷貝") 時, 可以考慮用 CPAN 的 Storable 模組。
作業
- 把 sched 的輸出改成一個 html table。
- 請回答 範例 最後面的問題。
- Q: 這句話會產生什麼資料結構?
@x = ({Apple=>3, Banana=>6, Guava=>4}, ["Mango", "Orange", "Cherry"]);
請畫圖表示。 並請試著取出其中部分元素或部分陣列/hash。
- 本頁最新版網址: https://frdm.cyut.edu.tw/~ckhung/b/pl/variable.php; 您所看到的版本: February 14 2012 10:32:25.
- 作者: 朝陽科技大學 資訊管理系 洪朝貴
- 寶貝你我的地球, 請 減少列印, 多用背面, 丟棄時做垃圾分類。
- 本文件以 Creative Commons Attribution-ShareAlike License 或以 Free Document License 方式公開授權大眾自由複製/修改/散佈。