前言
控制結構 (control structures) 是用來改變程式運行的順序,讓程式有一些基本的判斷功能。控制結構包括選擇 (selection) 和迭代 (iteration) 兩種;本文介紹 Groovy 中可用的控制結構。
Groovy 官網設明文件並未強調控制結構的使用,不過 Groovy 的官方教材 Groovy In Action, 2nd Edition, Manning 中的確有提到一些相關的內容。本文由筆者自行整理而成。
if
敘述
if
是最基礎的選擇結構,Groovy 承襲 Java,if
的使用方法也相同。if
的虛擬碼如下:
if (condition) {
// Run code here.
}
當 condition
為真時,會執行 if
區塊內的程式碼。
我們還可以加上反向的 else
敘述,其虛擬碼如下:
if (condition) {
// Run code here if `condition` is true
}
else {
// Run code here if `condition` is false
}
當 condition
為真時,執行 if
區塊內的程式碼。反之,則執行 else
區塊內的程式碼。
除了二元條件外,還可以加上多個 else if
區塊,就可以形成多元條件敘述,參考以下虛擬碼:
if (condition_1) {
// Do something_1
}
else if (condition_2) {
// Do something_2
}
else {
// Do something else
}
以本例來說,若符合 condition_1
時,程式會執行 something_1
,然後跳出整個 if
區塊。若 condition_1
不符時,程式會試著檢查 condition_2
,若符合 condition_2
,程式會執行 something_2
後跳出整個區塊。若上述條件皆不符合,程式會執行 else
區塊內的內容。
在 if
敘述中,if
是必需的,而 else if
和 else
是選擇性的。else if
可以一至多個,而 else
只能有一個,且要放在最後一個區塊。從語義上去想即可,不用死背這個規則。
以下是簡短的範例:
final random = new Random() /* 1 */
final n = random.nextInt(1 - (-1) + 1) - 1 /* 2 */
if (n > 0) { /* 3 */
println "n is larger than zero" /* 4 */
} /* 5 */
else if (n < 0) { /* 6 */
println "n is smaller than zero" /* 7 */
} /* 8 */
else { /* 9 */
println "n is equal to zero" /* 10 */
} /* 11 */
我們建立一個 java.util.Random 物件 random
(第 1 行)。接著,從 random
物件產生一個介於 -1
至 1
之間的隨機整數 n
(第 2 行)。
此處使用一個 if
敘述,由 n
的值來印出不同的訊息 (第 3 至 11 行)。
switch
敘述
switch
算是 if
的語法糖,在 C 家族的語言都會放。其虛擬碼如下:
switch (value) {
case a:
// Do something_a
break
case b:
// Do something_b
break
case c:
// Do something_c
break
default:
// Do something else
}
同樣也是會依序由上而下檢查,符合特定區塊時則進入該區塊。
switch
的雷就是會忘了放 break
,敘述就會繼續執行下去,這個特性稱為 fallthrough。Groovy 為了保留 Java 的習慣,並沒有改掉這項特性,程式設計者需自行注意。
參考範例如下:
final now = new Date() /* 1 */
final c = Calendar.getInstance(); /* 2 */
c.setTime(now); /* 3 */
final day = c.get(Calendar.DAY_OF_WEEK); /* 4 */
switch (day) { /* 5 */
case 6: /* 6 */
case 7: /* 7 */
println "Weekend" /* 8 */
break /* 9 */
case 5: /* 10 */
println "Thanks God. It's Friday" /* 11 */
break /* 12 */
case 3: /* 13 */
println "Hump day" /* 14 */
break /* 15 */
default: /* 16 */
println "Week" /* 17 */
break /* 18 */
} /* 19 */
我們建立 java.util.Date 物件 now
,代表程式運行時的的系統時間時間 (第 1 行)。
接著取得 java.util.Calender 物件 c
。因為一個程式只需單一的 Calendar
物件,故用單體 (singleton) 模式取得唯一的物件 c
(第 2 行)。
接著從 Calendar
物件 c
中使用相關的函式從 now
物件中取得 day
(day of week) (第 3、4 行)。
最後使用一個 switch
敘述,該敘述根據 day
的狀態來印出相對應的訊息 (第 5 至 19 行)。
switch
和 if
可代換,讀者可自行嘗試將本節範例用 if
改寫。
while
敘述
while
是基本的迴圈 (loop) 語法之一,主要用於以條件句為終止條件的迴圈。其虛擬碼如下:
while (condition) {
// Run code here repeatedly while `condition` is true
}
只要 condition
為真,while
區塊內的程式碼就會重覆執行。
參考簡短範例如下:
def i = 10
while (i > 0) {
println "Count down ${i}"
i--
}
以下是無窮迴圈 (infinite loop):
while (true) {
// Run code here repeated indifintely.
}
如同其名,無窮迴圈內的程式碼區塊會不斷地執行。除了新手偶爾寫錯造成無限迴圈外,實務上我們會用一些中止條件去改變迴圈的行進,詳見下文。
for
敘述
for
有多種使用方式,一種是使用計數器 (counter) 來走訪,一種是使用迭代器 (iterator);在 Groovy 中,兩種方式都可以。
傳統的 C 風格 for
使用計數器,參考下例:
for (def i = 10; i > 0; i--) {
println "Count down ${i}"
}
Groovy 提供 range 語法,可做為迭代器,參考下例:
for (i in 1..10) {
assert 1 <= i && i <= 10
}
也可以用陣列等容器做為迭代器,詳見後文。
迭代器 (Iterator)
迭代器是指等效於迴圈的語句,主要的好處是不需知道迭代器內部的實作,也不需要使用額外的計數器。Groovy 的迭代器可用容器 (collections) 和 closure 走訪,很大一部分是向 Ruby 致敬。
以下的迭代器指定走訪區塊 3 次:
3.times { println "Hello World" }
以下的迭代器走訪一個 range:
(1..10).each { i -> println i }
以下的迭代器走訪一個陣列,並附帶索引:
["a", "b", "c"].eachWithIndex { e, i ->
println "${i}: ${e}"
}
一開始寫程式時,通常會不太習慣迭代器,而會想用傳統的 for
和 while
迴圈;使用那種方式來迭代其實都可以。一開始可以先用傳統的迴圈來寫,寫一段時間後再慢慢把程式碼改寫 (重構) 成迭代器即可。
用 continue
或 break
改變迴圈行進
continue
可在迴圈進行到中途時,跳過某一次迭代。參考下例:
for (def i = 1; i <= 10; i++) {
if (i % 2 != 0) {
continue
}
println i
}
這個程式在輪到奇時數會跳過剩下的程式碼,故只會印出偶數。
break
則可以中斷迴圈。參考下例:
for (def i = 1; i <= 10; i++) {
if (i > 5) {
break
}
println i
}
這個程式在 i
為 6
時迴圈會中斷,故只印出 1
至 5
。
(選讀) 終極密碼 (Da Vinci Code)
先前的程式碼偏短,沒有使用控制結構的感覺。本節用控制結構寫一個小型命令列遊戲,讓小伙伴感受一下使用控制結構的方式。本範例稍微超出目前的範圍,但程式碼不長,請小伙伴試著閱讀一下。
以下是使用此範例程式的過程:
$ groovy da-vinci-code.groovy
Input a number (1-100): 50
Too large
Input a number (1-50): 25
Too large
Input a number (1-25): 13
Too large
Input a number (1-13): 7
Too small
Input a number (7-13): 10
Too small
Input a number (10-13): 11
Too small
Input a number (11-13): 12
You guess right
這裡列出範例實作,下文會講解程式碼:
/* Set the initial range of a answer. */ /* 1 */
def min = 1 /* 2 */
def max = 100 /* 3 */
/* Get a random answer between `min` and `max`. */ /* 4 */
def answer = /* 5 */
min + (new Random()).nextInt(max - min + 1) /* 6 */
/* Run the game loop. */ /* 7 */
while (true) { /* 8 */
/* Try to receive a valid input from the user. */ /* 9 */
def guess /* 10 */
while (true) { /* 11 */
/* Prompt the user for an input. */ /* 12 */
def input = System /* 13 */
.console() /* 14 */
.readLine "Input a number (${min}-${max}): " /* 15 */
try { /* 16 */
/* Parse the input. */ /* 17 */
guess = Integer.parseInt(input) /* 18 */
} /* 19 */
catch (e) { /* 20 */
/* It fails to parse the input.
Skip to next iteration. */
println "Invalid number: ${input}" /* 21 */
continue /* 22 */
} /* 23 */
/* The guess out of range.
Skip to next iteration. */
if (!(min <= guess && guess <= max)) { /* 24 */
println "Number out of range: ${guess}" /* 25 */
continue /* 26 */
} /* 27 */
/* The guess is valid. Exit the input loop. */ /* 28 */
break /* 29 */
} /* 30 */
if (guess < answer) { /* 31 */
println "Too small" /* 32 */
min = guess /* 33 */
} /* 34 */
else if (guess > answer) { /* 35 */
println "Too large" /* 36 */
max = guess /* 37 */
} /* 38 */
else { /* 39 */
println "You guess right" /* 40 */
/* The guess is equal to the answer.
Exit the game loop. */
break /* 41 */
} /* 42 */
} /* 43 */
為了簡化程式,此範例程式直接寫入固定的答案範圍 (第 2、3 行)。根據此範圍,取得隨機的答案 (第 5、6 行)。Groovy 會自動引入 java.util.Random 類別,程式設計者不用手動引入。
接著進入遊戲迴圈 (game loop),在使用者猜對前,都不離開這個迴圈 (第 8 至 43 行)。
每輪迭代要先取得合理的 (valid) 猜測 (第 10 至 30 行)。使用 java.io.Console 物件的 readLine 函式從終端機讀入一行輸入 (第 13 至 15 行)。試著用 java.lang.Integer 物件的 parseInt 函式解析輸入 (第 16 至 23 行)。當無法解析輸入時,跳到下一輪迭代 (第 22 行)。
確認輸入是合理的之後,要確認猜測符合上下限 (第 24 至 27 行)。當輸入不符合上下限時,跳到下一輪迭代 (第 26 行)。
然後判斷使用者在此輪是否猜對 (第 31 至 42 行)。當使用者猜錯時,更動上下限並重新迭代 (第 31 至 38 行)。當使用者猜對時,結束遊戲迴圈 (第 39 至 42 行)。