前言
控制結構 (control structures) 用來調整程式行進的方向,幾乎每個高階程式語言都會有這些特性。控制結構分為選擇 (selection) 和迭代 (iteration) 兩種。本文介紹 Perl 的控制結構。
真值和偽值
Perl 沒有真正的真值 (true value) 和偽值 (false value),程式內部會自動判定值的真偽。以下值認定為偽 (falsy):
undef
:未定義的值0
:數字零 (zero)""
:空字串"0"
:僅包含零的字串- 空陣列
- 空雜湊
其他的值視為真 (truely)。
if
敘述
if
依據給定的條件決定是否要執行特定程式碼區塊 (block)。以下是 if
的虛擬碼:
if (cond) {
# Run code here if `cond` is true.
}
只有當 cond
為真時才會執行該程式碼區塊。
if
還可以 (選擇性) 搭配反向的 else
敘述,形成二元敘述。其虛擬碼如下:
if (cond) {
# Run code here if `cond` is true.
}
else {
# Run code here if `cond` is not true.
}
當 cond
為真時,就執行 if
區塊內的敘述。反之,則執行 else
區塊內的敘述。
除了使用 else
外,還可以搭配一到多個 elsif
形成多元敘述。其虛擬碼如下:
if (condition_a) {
# Do something a
}
elsif (condition_b) {
# Do something b
}
else {
# Do something else
}
這裡面有三個區塊,若符合 if
的 condition_a
條件,會執行 something a
所在的程式碼,之後跳離這個敘述。若不符合 condition_a
,會判斷 elsif
區塊的 condition_b
條件,若符合該條件,則執行 something b
所在的程式碼,之後跳離這個敘述。若先前的條件皆不符合,則會執行 else
區塊內的程式碼後離開此敘述。
在 if
敘述中,只有 if
區塊是必備的,elsif
和 else
區塊都是選擇性的。elsif
區塊可以重覆出現多次,而 else
區塊只能出現一次,且要放在 if
敘述的最尾端。這些規則用語義去想即可,不要死背。
以下是 if
敘述的實例:
my $n = int(rand(10)) + 1;
if ($n % 5 == 0) {
print "$n is divisible by 5\n";
}
elsif ($n % 3 == 0) {
print "$n is divisible by 3\n";
}
elsif ($n % 2 == 0) {
print "$n is divisible by 2\n";
}
else {
print "\$n is $n\n";
}
rand() 函式會回傳一個介於 0
至 1
之間的浮點數,而 rand(10)
會回傳一個介於 0
和 10
之間的浮點數。int() 函式會將傳入的浮點數去掉小數後的部分。以本例來說,int(rand(10)) + 1
會得到一個介於 1
和 10
之間的整數。
接著,根據 $n
的值印出不同的訊息。由於 if
在判斷條件上有先後順序,故我們將除數由大至小排列。
unless
敘述
unless
用法和 if
相同,而語義則相反。由於 unless
本身隱含著否定的意味,在複合條件句上會比較難寫,除非語意適合,一般建議用 if
加上否定條件,在程式碼上較易於理解。以下是 unless
的實例:
my $n = int(rand(10)) + 1;
unless ($n % 2 != 0) {
print "$n is even\n";
}
else {
print "$n is odd\n";
}
given
敘述
given
是 Perl 5.10 版後加入的新語法,主要用來提供類似 C 家族語言的 switch,算是 if
的語法糖。以下是一個短例:
use feature "switch";
my $day = (localtime)[6];
given ($day) {
when ((0, 6)) {
print "Weekend\n";
}
when (5) {
print "Thank God. It's Friday.\n";
}
when (3) {
print "Hump day\n";
}
default {
print "Week";
}
}
由於 given
是一個新的語法,我們要額外使用 use feature "switch";
開啟這項特性,而且 Perl 的版本不能太舊。5.10 是西元 2007 年時發佈的,現在大部分機器上的 Perl 版本都比較個版本還新。
localtime() 函式會得到一個代表時間的串列,其中的第七項數值就代表該週的某一天 (the day of a week)。我們將該數值取出,並存入 $day
變數。
接著,在 given
區塊中判斷 $day
的值,並給予相對應的動作。在 given
區塊中,每個 when
區塊會對應一個值,若符合特定值,則執行該區塊,然後離開 given
敘述。若所有 when
條件都不符合而 default
區塊存在時,則執行 default
區塊。default
區塊是選擇性的,不加也可行。
在本例的第一個 when
敘述中,我們放入一個串列 (0, 6)
,只要 $day
符合該串列其中一個值,即會執行該區塊內的動作。我們會在後續的文章介紹串列。
while
敘述
while
用於執行次數未定的迴圈,其虛擬碼如下:
while (condition) {
# Do something.
}
在 condition
條件成立的前提下,會反覆執行 something
區塊內的程式碼,直到不符合 condition
條件時,才結束 while
迴圈。
以下是實例:
my $n = 10;
while ($n > 0) {
print "Count down $n\n";
$n--;
}
也可以用 continue
改寫如下:
my $n = 10;
while ($n > 0) {
print "Count down $n\n";
} continue {
$n--;
}
這種寫法的好處是可將計數器的部分和其他程式碼分開,易於閱讀。
以下 while
迴圈為無窮迴圈:
while (1) {
# Do something.
}
如果我們不中斷這個迴圈,這個迴圈會不斷地進行下去;有時候無窮迴圈是刻意寫的,有時候則是程式寫錯引發的臭蟲 (bug)。我們會在後文介紹一些改變迴圈進行的方法。
until
敘述
until
和 while
用法相同但語意相反。由於 until
本身隱含著否定的意味,在複合條件句上會比較難寫,除非某段程式碼在語意上剛好符合,用 while
改寫比較會比較好閱讀。以下是 until
的實例:
my $n = 10;
until ($n <= 0) {
print "Count down $n\n";
$n--;
}
同樣地,也可以用 continue
改寫如下:
my $n = 10;
until ($n <= 0) {
print "Count down $n\n";
} continue {
$n--;
}
for
敘述
在 Perl 之中,for
有兩種用法,一種是搭配計數器 (counter),一種是搭配串列 (list)。
使用計數器的 for
這種用法源自於 C 家族語言。實例如下:
for (my $i = 1; $i <= 10; $i++) {
print "\$i is $i\n";
}
其實 for
迴圈可以改寫成等效的 while
迴圈。沿續先前的例子:
{
my $i = 1;
while ($i <= 10) {
print "\$i is $i\n";
} continue {
$i++;
}
}
我們用額外的區塊是為了避免出現新的變數,汙染命名空間。
for (;;)
和 while(1)
同義,皆是無窮迴圈:
for (;;) {
# Do something here.
}
使用串列的 for
我們留到介紹陣列及串列時一併講解。
foreach
foreach
和 for
是同義字,可交互使用。有些 Perl 程式人會用 for
搭配計數器而 foreach
搭配串列,這只是撰碼風格,不是硬性規定。
改變迴圈執行順序
next
敘述
next
用於略過當次的迭代,繼續下一輪的迴圈,通常會和 if
搭配使用。如下例:
for (my $n = 10; $n > 0; $n--) {
next if $n % 2 != 0;
print "Count down $n\n";
}
redo
敘述
redo
用於重新開始同一次迭代。如下例:
my $n;
while (1) {
$n = int(rand(10)) + 1;
$n % 2 == 0 or redo;
last;
}
print $n, "\n";
在這個程式中,當 $n
不為偶數時就重新取一個介於 1 至 10 的隨機整數,直到 $n
為偶數時才跳離此迴圈。
last
敘述
last
用於跳離迴圈,不再執行該迴圈,通常會和 if
搭配使用。如下例:
for (my $i = 10; $i > 0; $i--) {
last if $i <= 5;
print "Count down $i\n";
}
goto
敘述
goto
不限於迴圈中使用,可跳到程式中任意位置。下例用 goto
模擬 while
迴圈:
my $n;
RESTART:
$n = int(rand(10)) + 1;
$n % 2 == 0 or goto RESTART;
print $n, "\n";
在這個程式中,當 $n
不為偶數時,就跳到 RESTART
所在的地方。可以和先前 redo
的版本相互比較。
濫用 goto
會造成程式難以維護,故現在較少使用 goto
而會使用前述的 next
、redo
、last
等。
後位修飾 (Postfix Modifier)
後位修飾是一種控制結構的變形語法,可用在 if
、unless
、for
、while
、until
等,主要的用意是讓程式碼變簡潔。後位修飾常用於 Perl one liner,在一般命令稿中相對少用。以下是實例:
my $n = int(rand(10)) + 1;
print "$n is even\n" if $n % 2 == 0;