前言 :
這一章我們將看到許多用來撰寫 Perl 程式的技巧. 這些技巧不會提升 Perl 語言本身的威力, 但卻能讓工作更容易完成.
unless 控制結構 :
if 控制結構裡的程式區塊, 只有當條件運算式為真才會執行. 如果你想讓程式區塊在條件為 "假" 時執行, 請將 if 換成 unless :
使用 unless, 表示除非這個條件式為真, 否則就執行這段程式碼. 它和 if 測試一模一樣, 只是條件相反. 另一種幫助記憶的方法就是將它想成獨立的 else 子句. 也就是 if 條件不執行的時候, 就執行 unless 子句.
- 伴隨著 unless 的 else 子句
你甚至可以在 unless 之後加上一個 else 子句. 雖然這是在語法允許的, 但卻有潛在的語意混淆 :
其實這用 if/else 來寫會直觀點. 通常使用 unless 就是用來確認條件為否的狀態. 因此當你想要條件為真/否都要處理, 建議使用 if/else 以方便維護.
until 控制結構 :
有時候你會想要顛倒 while 迴圈的條件式, 那麼請使用 until :
這個迴圈會一直執行, 直到條件式為 "true". 它只不過是個改裝過的 while 迴圈罷了 ; 兩者之間唯一的差別在於 , until 會在條件為 "假" 時反覆執行. 就像 if 和 unless 的例子, 你可以用否定條件的方式將任何 until 迴圈改寫成 while 迴圈.
運算式修飾符 :
為了簡化表達的方式, 運算式後面可以接著一個用來控制它的修飾符. 舉例來說, if 修飾符的作用跟 if 區塊很類似 :
print "$n is a minus number.\n" if $n < 0;
去掉圓括號與大括號, 除了節省了一些打字時間外, 它的執行結果和底下程式完全相同 :
注意到即使條件式寫在後面, 它仍然會先執行. 因此在解讀 Perl 程式碼時, 我們必須跟 Perl 內部編譯器一樣, 把陳敘句全部讀完, 才有辦法判斷它的真意. 還有其他修飾符, 範例如下 :
用這種"倒裝句" 撰寫程式, 可以把陳敘句中重要的部分放在前面. 還有運算修飾符的兩邊只能各放一個運算式, 所以不能寫成 "某式 if 某式 while 某式 until 某式 unless 某式" , 這樣容易造成混淆. 此外修飾符的左邊也不能放多個陳敘句. 如果你兩邊都需要一個以上的運算式, 就只能使用傳統的做法.
未修飾的區塊控制結構 :
所謂的 "未修飾" (naked) 區塊就是沒有關鍵字或條件的區塊, 假設你有一個 while 迴圈如下 :
然後拿走關鍵字 while 還有條件式, 就會得到一個未修飾區塊 :
未修飾區塊就像一個 while/foreach 迴圈, 可是它不支援循環執行功能 ; 區塊的內容只會執行一次, 它是個不循環的迴圈!
我們稍後會看到未修飾區塊的其他用法. 為臨時語彙變數提供一個有效範圍, 是它其中一項特色 :
在這個區塊裡, $n 與 $root 都是以此區塊為有效範圍的臨時變數. 如果某個變數只會在某幾列程式使用, 你可以把這幾列放到一個未修飾區塊內, 並且在區塊內宣告變數. 當然如果我們稍後還要用到 $n 或是 $root 的值, 那我們就必須在較大的有效範圍內宣告它們.
elsif 子句 :
許多情況下, 你得逐項檢查一系列的條件式, 看看其中哪個為真. 你可以透過 if 控制結構的 elsif 字句來完此事 :
自動遞增與自動遞減 :
讓純量變數往上或往下數一, 是很常見的用途. 因為它們經常出現, 所以就像其它很常用的運算式一樣, 有特別的簡寫.
自動遞增運算符 (++) 會將純量變數加一, 這跟 C 與其他相似程式語言中同名的算符一樣 :
跟其他將變數加一的方法一樣, 它在有需要時會建立新的純量 :
第一次處理 foreach 迴圈時. $count{$_} 會自動遞增. 先是 $count{"fred"}, 因此它會從 undef (因為該雜湊裡原先沒有 fred) 變成1; 迴圈下一次執行時, $count{"barney"} 會變成一. 每次迴圈處理完後, %count 中的某個元素就會自動遞增, 當然也有可能被建立. 這是個快速而簡易的方法, 用來檢查串列中有哪些項目, 並自動計算每個項目的出現次數. 同樣的, 自動遞減運算符 (--) 會將純量變數減一.
- 自動遞增的值
你可以在從變數取的值之際, 同時變更變數的值. 在變數名稱之前置放 ++ 運算符, 代表你要先遞增變數的值, 再取得它的新值. 這是一個前置遞增 (preincrement) :
相同的道理也可以用在 前置遞減 (predecrement). 如果將變數名稱放在前面, 之後在接遞增或遞減運算符. 這種做法稱為後置遞增/後置遞減 :
for/foreach 控制結構 :
在 Perl 得剖析器裡, foreach 和 for這兩個關鍵字是等價的. 也就是說當 Perl 看到其中之一時, 換成另外一個也不會有差別. Perl 可以從圓括號的內容判斷出你的意圖. 如果裡面有兩個分號, 它會被當成 for 迴圈. 否則其實它是個 foreach 迴圈 :
這其實是個 foreach 迴圈, 只是寫成 for 的形式. 除了初學者的程式碼, 它通常會被寫成 for 以節省多打四個字母的時間.
迴圈控制 :
看到這裡, 也許你有注意到, Perl 是一個 "結構化" 的程式語言. 尤其是 Perl 程式的任何區塊都只有一個入口, 也就是區塊的頂端. 不過跟前面介紹過的結構, 也時後會需要更多樣式的控制方式. 舉例來說, 你也許需要建立一個跟 while 迴圈相似, 但至少要求執行一次. 要不然你或許偶而需要提早離開某個區塊程式. Perl 有三個迴圈控制運算符, 你可以在迴圈裡使用它們, 好滿足你的需求.
- last 運算符
last 運算符會立即終止迴圈的執行. 它是回圈的緊急出口. 當你碰上 last, 迴圈就會因此而結束 :
只要輸入 __END__ 記號, 這個迴圈就會結束. 在 Perl 中有五種迴圈區塊 : for, foreach, while, until 以及未修飾區塊. if 區塊或副常式的大括號不算數. last 運算符會對整個迴圈區塊都起作用.
- next 運算符
有時候你並不想離開迴圈, 但是卻想立刻結束目前這次操作. 這就是 next 運算符的用處 ; 它會跳到目前迴圈區塊的底端. 接下來程式將會繼續執行迴圈的下次操作 :
跟 last 一樣, next 也可以用在這五種迴圈區域 : for, foreach, while, until 或是未修飾區塊, 同樣地如果有許多層的迴圈區塊, next 只會對最內層起作用.
- redo 運算符
迴圈控制運算符的第三個成員是 redo. 它會跳回目前的迴圈區塊的頂端 (不會前進到迴圈的下一次操作). 範例如下 :
和另外兩個運算符一樣, redo 在這五種迴圈都可以作用, 並且在迴圈區塊是巢狀的情況下, 只會對最內層的迴圈起作用. next 和 redo 兩者之間最大的不同在於 next 會正常的繼續下一次操作, 但是 redo 則會重新執行該次操作.
- 帶有標籤的區塊
當你需要對外層的迴圈進行操作時, 請使用標籤 (Label). 在 Perl 裡標籤和其他識別字一樣, 由字母, 數字及底線符號組成, 但不能以數字開頭 - 不過由於它們並沒有前置字符, 標籤可能會和內建函式名稱, 甚至是與你自己的函式名稱互相混淆. 所以將標籤命名為 print 或是 if 是很糟糕的選擇. 正因為如此, Larry 建議將標籤都取名為大寫字母. 這樣不僅能確保它不會跟其他識別字相牴觸, 也使得它在程式中的位置更容易被找到.
要對某個迴圈加上標籤, 只要將標籤及一個冒號放在迴圈前面就行了. 之後再迴圈裡若有需要, 你可以在 last, next 或 redo 的後面加上標籤名稱 :
為了增進可讀性, 通常建議的方法是把標籤靠左對齊, 即使目前的程式碼已經縮排過. 這裡注意到, 標籤是對整個區塊命名, 而不是用來在程式中建立一個目標點. 另外通常應該以名詞來為迴圈命名. 也就是說我們可能叫它 word. 因為它每次會處理一個單字. 如此一來要描述 "next WORD" 或者 "redo LINE" 之類的事就很直覺.
三元運算符 ?:
三元運算符就像是將一個 if-then-else 測試全部放在一個運算式裡. 由於使用時需要三個運算元, 所以稱為 "三元" (ternary) 運算符. 它看起來像底下 :
條件運算式 ? 若條件為真的運算式 : 若為假所執行的運算式
首先執行 "條件運算式", 看看究竟是真還是假. 如果為真則執行第二個運算式 ; 否則就執行第三個運算式. 每次使用都會執行右邊兩個運算式中的一個, 另一個則會被略過. 在下面的例子裡, 副常式 &is_weekend 的執行結果, 決定了哪一個字串運算式會被賦值給變數 :
下面的例子, 我們會計算並印出一個平均值 - 或是在求不出平均值時, 用一列連字符來表示 :
底下是一個常見的技巧, 用來撰寫一個俐落的多重分岔程式碼 : (也可以使用 if/else 結構)
邏輯運算符 :
Perl 擁有處理布林 (真/假) 值所有必要的邏輯算符. 比方說以邏輯 AND 運算符 (&&) 與 邏輯 OR 運算符 (||) 來連結多項邏輯測試 :
- 短路運算符
在 Perl 裡, 短路運算符所求得的值不僅是布林值, 也是它上一次執行的值. 它們的效果是一樣的, 因為當整個測試應該為真的時, 上一次執行的部分一定為真 ; 而且當整個測試應該為假時, 上一次測試結果一定為假. 不過當它做為傳回值時, 會更有用處. 例如選擇預設值, 就是邏輯 OR 運算符的粽多用途之一 :
my $last_name = $last_name{$someone} || '(No last name)'; # 當沒有某人的 lase name, 則放入 '(No last name)'
上面的用法即當 $lase_name{$someone} 的值為 "假" (空字串 or undef), 則使用預設值 '(No last name)'. 但有時你只希望當遇到 undef 才使用預設值, 這時你可以使用三元運算符來修正此問題 :
- defined-or 運算符
上面你看到了我們使用 || 運算符來提供預設值. 忽略特殊的情況 ; 即使所定義的值為假值, 仍舊是一個值, 接著你看到了三元運算符的版本, 為了讓你少打幾個字, Perl 5.10 提供了 defined-or 運算符 // , 當它發現左邊的值有定義便會執行短路的版本, 而不管左邊的值為真或假 (即使某人 $someone 的 last name 為 0) :
而另一個 defined-or 的運用就是當你想在變數的值尚未定義時, 才提供一個預設值, 但如果值已經定義, 則不用理會它. 這裡我們用環境變數 VERBOSE 為範例 :
下面使用 // 的範例中. 你可以看到當 $try 的值為何時, 會得到 default 的值 :
輸出結果如下, 當 $try 為 undef 你會得到 default 字串 :
這一章我們將看到許多用來撰寫 Perl 程式的技巧. 這些技巧不會提升 Perl 語言本身的威力, 但卻能讓工作更容易完成.
unless 控制結構 :
if 控制結構裡的程式區塊, 只有當條件運算式為真才會執行. 如果你想讓程式區塊在條件為 "假" 時執行, 請將 if 換成 unless :
- $kw = "test";
- unless ($kw =~ /^[A-Z_]\w*$/) {
- print "$kw doesn't seems as Perl's identified word!\n";
- }
- 伴隨著 unless 的 else 子句
你甚至可以在 unless 之後加上一個 else 子句. 雖然這是在語法允許的, 但卻有潛在的語意混淆 :
- $mon = "Feb";
- unless ($mon =~ /^Feb/) {
- print "This month has at least 30 days.\n";
- } else {
- print "Can you tell why?\n";
- }
until 控制結構 :
有時候你會想要顛倒 while 迴圈的條件式, 那麼請使用 until :
- $i = 50;
- $j = 1;
- until ($j > $i) {
- $j *= 2;
- }
- print "Rst = $j\n";
運算式修飾符 :
為了簡化表達的方式, 運算式後面可以接著一個用來控制它的修飾符. 舉例來說, if 修飾符的作用跟 if 區塊很類似 :
print "$n is a minus number.\n" if $n < 0;
去掉圓括號與大括號, 除了節省了一些打字時間外, 它的執行結果和底下程式完全相同 :
- if($n<0) {
- print "$n is a minus number\n";
- }
- &error("Wrong input!") unless &valid($input);
- $i += 2 until $i > $j;
- print " ", ($n += 2) while $n < 10;
- &greet($_) foreach @person;
未修飾的區塊控制結構 :
所謂的 "未修飾" (naked) 區塊就是沒有關鍵字或條件的區塊, 假設你有一個 while 迴圈如下 :
- while(條件式) {
- 程式主體;
- 程式主體;
- 程式主體;
- }
- {
- 程式主體;
- 程式主體;
- 程式主體;
- }
我們稍後會看到未修飾區塊的其他用法. 為臨時語彙變數提供一個有效範圍, 是它其中一項特色 :
- {
- print "Please give a number: ";
- chomp(my $n =
); - my $root = sqrt $n;
- print "$n root to be $root\n";
- }
elsif 子句 :
許多情況下, 你得逐項檢查一系列的條件式, 看看其中哪個為真. 你可以透過 if 控制結構的 elsif 字句來完此事 :
- if(! defined $dino) {
- print "This value is undef.\n";
- } elsif ($dino =~ /^-?\d+\.?$/) {
- print "This is integer.\n"
- } elsif ($dino =~ /^-?\d+\.?\d+$/) {
- print "This is a simple float number.\n";
- } else {
- print "Others\n";
- }
讓純量變數往上或往下數一, 是很常見的用途. 因為它們經常出現, 所以就像其它很常用的運算式一樣, 有特別的簡寫.
自動遞增運算符 (++) 會將純量變數加一, 這跟 C 與其他相似程式語言中同名的算符一樣 :
- my $bedrock = 42;
- $bedrock++ # 將 $bedrock 加一, 現在是 43
- my @people = qw{ fred barney fred wilma dino barney fred pebbles };
- my %count;
- $count{$_}++ foreach @people; # 必要時建立新的鍵與值
- 自動遞增的值
你可以在從變數取的值之際, 同時變更變數的值. 在變數名稱之前置放 ++ 運算符, 代表你要先遞增變數的值, 再取得它的新值. 這是一個前置遞增 (preincrement) :
相同的道理也可以用在 前置遞減 (predecrement). 如果將變數名稱放在前面, 之後在接遞增或遞減運算符. 這種做法稱為後置遞增/後置遞減 :
for/foreach 控制結構 :
在 Perl 得剖析器裡, foreach 和 for這兩個關鍵字是等價的. 也就是說當 Perl 看到其中之一時, 換成另外一個也不會有差別. Perl 可以從圓括號的內容判斷出你的意圖. 如果裡面有兩個分號, 它會被當成 for 迴圈. 否則其實它是個 foreach 迴圈 :
- for(1..10) { # 從 1 到 10 的 foreach 迴圈
- print "Count $_!\n";
- }
迴圈控制 :
看到這裡, 也許你有注意到, Perl 是一個 "結構化" 的程式語言. 尤其是 Perl 程式的任何區塊都只有一個入口, 也就是區塊的頂端. 不過跟前面介紹過的結構, 也時後會需要更多樣式的控制方式. 舉例來說, 你也許需要建立一個跟 while 迴圈相似, 但至少要求執行一次. 要不然你或許偶而需要提早離開某個區塊程式. Perl 有三個迴圈控制運算符, 你可以在迴圈裡使用它們, 好滿足你的需求.
- last 運算符
last 運算符會立即終止迴圈的執行. 它是回圈的緊急出口. 當你碰上 last, 迴圈就會因此而結束 :
- while(
) { - if(/__END__/) {
- # 立即停止迴圈
- last;
- } elsif(/john/) {
- print;
- }
- }
- ## 碰上 last 之後跳到這裡
- next 運算符
有時候你並不想離開迴圈, 但是卻想立刻結束目前這次操作. 這就是 next 運算符的用處 ; 它會跳到目前迴圈區塊的底端. 接下來程式將會繼續執行迴圈的下次操作 :
- while(<>) {
- foreach(split) {
- $total++;
- next if /\W/;
- $valid++;
- $count{$_}++;
- ## 碰到 next 之後會跳到這裡 ##
- }
- }
- redo 運算符
迴圈控制運算符的第三個成員是 redo. 它會跳回目前的迴圈區塊的頂端 (不會前進到迴圈的下一次操作). 範例如下 :
- my @words = qw{ fred barney peddles dino wilma betty };
- my $errors = 0;
- foreach(@words) {
- ## 碰到 redo 後會到這裡 ##
- print "Please key in:";
- chomp(my $try=
); - if($try ne $_) {
- print "Sorry - wrong word.\n\n";
- $errors++;
- redo; # 跳到迴圈頂
- }
- }
- print "Over. You made $errors mistakes.\n";
- 帶有標籤的區塊
當你需要對外層的迴圈進行操作時, 請使用標籤 (Label). 在 Perl 裡標籤和其他識別字一樣, 由字母, 數字及底線符號組成, 但不能以數字開頭 - 不過由於它們並沒有前置字符, 標籤可能會和內建函式名稱, 甚至是與你自己的函式名稱互相混淆. 所以將標籤命名為 print 或是 if 是很糟糕的選擇. 正因為如此, Larry 建議將標籤都取名為大寫字母. 這樣不僅能確保它不會跟其他識別字相牴觸, 也使得它在程式中的位置更容易被找到.
要對某個迴圈加上標籤, 只要將標籤及一個冒號放在迴圈前面就行了. 之後再迴圈裡若有需要, 你可以在 last, next 或 redo 的後面加上標籤名稱 :
- LINE: while(<>) {
- foreach(split) {
- last LINE if /__END__/; # 跳到 LINE 迴圈
- ...;
- }
- }
三元運算符 ?:
三元運算符就像是將一個 if-then-else 測試全部放在一個運算式裡. 由於使用時需要三個運算元, 所以稱為 "三元" (ternary) 運算符. 它看起來像底下 :
條件運算式 ? 若條件為真的運算式 : 若為假所執行的運算式
首先執行 "條件運算式", 看看究竟是真還是假. 如果為真則執行第二個運算式 ; 否則就執行第三個運算式. 每次使用都會執行右邊兩個運算式中的一個, 另一個則會被略過. 在下面的例子裡, 副常式 &is_weekend 的執行結果, 決定了哪一個字串運算式會被賦值給變數 :
- my $location = &is_weekend($day) ? "home" : "work";
- my $average = $n ? ($total/$n) : "--------";
- print "平均值 : $average\n";
- my $size =
- ($width < 10) ? "小" :
- ($width < 20) ? "中" :
- ($width < 50) ? "大" :
- "超大"; # 預設值
邏輯運算符 :
Perl 擁有處理布林 (真/假) 值所有必要的邏輯算符. 比方說以邏輯 AND 運算符 (&&) 與 邏輯 OR 運算符 (||) 來連結多項邏輯測試 :
- if($dessert{'cake'} && $dessert{'ice cream'}) {
- # Both true
- print "Have cake and ice cream!!\n";
- } elsif ($dessert{'cake'} || $dessert{'ice cream'}) {
- print "Not bad...\n";
- } else {
- # No one true.
- }
在 Perl 裡, 短路運算符所求得的值不僅是布林值, 也是它上一次執行的值. 它們的效果是一樣的, 因為當整個測試應該為真的時, 上一次執行的部分一定為真 ; 而且當整個測試應該為假時, 上一次測試結果一定為假. 不過當它做為傳回值時, 會更有用處. 例如選擇預設值, 就是邏輯 OR 運算符的粽多用途之一 :
my $last_name = $last_name{$someone} || '(No last name)'; # 當沒有某人的 lase name, 則放入 '(No last name)'
上面的用法即當 $lase_name{$someone} 的值為 "假" (空字串 or undef), 則使用預設值 '(No last name)'. 但有時你只希望當遇到 undef 才使用預設值, 這時你可以使用三元運算符來修正此問題 :
- my $last_name = defined $last_name{$someone} ?
- $lase_name{$someone} : '(No last name)';
上面你看到了我們使用 || 運算符來提供預設值. 忽略特殊的情況 ; 即使所定義的值為假值, 仍舊是一個值, 接著你看到了三元運算符的版本, 為了讓你少打幾個字, Perl 5.10 提供了 defined-or 運算符 // , 當它發現左邊的值有定義便會執行短路的版本, 而不管左邊的值為真或假 (即使某人 $someone 的 last name 為 0) :
而另一個 defined-or 的運用就是當你想在變數的值尚未定義時, 才提供一個預設值, 但如果值已經定義, 則不用理會它. 這裡我們用環境變數 VERBOSE 為範例 :
- use 5.010;
- my $Verbose = $ENV{VERBOSE} // 1;
- print "I can talk to you!\n" if Verbose;
- use 5.010;
- foreach my $try (0, undef, '0', 1, 25) {
- print "Trying [$try] ---> ";
- my $value = $try // 'default';
- say "\tgot [$value]";
- }
This message was edited 8 times. Last update was at 30/08/2010 10:21:23
沒有留言:
張貼留言