switch 陳述
December 19, 2021在〈if 陳述〉中有個成績比對的範例,因為 Toy Lang 的 if...else
規定必須是右上 {
左下 }
,而且沒有提供 if...elif...else
這類的語法,因此會形成明顯的巢狀。
Toy 語法
實際上,該範例對成績區分為 A ~ D 與不及格,也就是列舉了五個可能的情況,基本上有關於列舉的匹配,就可以使用 switch
來處理:
score = Number.parseInt(input('輸入分數:'))
switch Number.parseInt(score / 10) {
case 10, 9
println('得 A')
case 8
println('得 B')
case 7
println('得 C')
case 6
println('得 D')
default
println('不及格')
}
A ~ D 與不及格怎麼列舉呢?主要就是根據分數的十位數字而定,因此這個範例適用於 switch
來列舉,switch
右邊運算出來的值,會與 case
的值逐一比對,若符合就執行 case
對應的陳述。
如你所見,Toy Lang 的 case
可以同時列舉多個值,值與值之間使用逗號區隔,可用於比對數值、字串,如果沒有任何值比對成功,就會執行 default
部份的陳述。
Toy 實作
如果你已經看過〈if 陳述〉,大概可以想像出 switch
是如何實作出來的,沒錯,switch
基本上也是個陳述句的容器,差別在於 switch
存放的陳述會有多個區段,基本上會像是:
class Switch {
constructor(switchValue, cases, defaultStmt) {
this.switchValue = switchValue;
this.cases = cases;
this.defaultStmt = defaultStmt;
}
evaluate(context) {
const value = this.switchValue.evaluate(context);
return compareCases(context, value, this.cases) || this.defaultStmt.evaluate(context);
}
}
function compareCases(context, switchValue, cases) {
if(cases.length === 0) {
return false; // no matched case
}
const cazeValues = cases[0][0];
const cazeStmt = cases[0][1];
return compareCaseValues(context, switchValue, cazeValues, cazeStmt) ||
compareCases(context, switchValue, cases.slice(1));
}
function compareCaseValues(context, switchValue, cazeValues, cazeStmt) {
if(cazeValues.length === 0) {
return false; // no matched value
}
const v = cazeValues[0].evaluate(context);
if(v.value === switchValue.value) {
return cazeStmt.evaluate(context);
}
return compareCaseValues(context, switchValue, cazeValues.slice(1), cazeStmt);
}
要比對的 case
會有多個,而每個 case
會有一個以上的列舉值以及對應的陳述,這是 cases
裏儲存的,cases[n]
代表著第 n
個 case
,cases[n][0]
是列舉值清單,cases[n][1]
是陳述句清單,呃!用巢狀的清單好像不是很好,應該要定義個專用類別會比較清楚…這件事就交給你了…XD
簡單來說,取得 switch
要被比對的值之後,逐一與 case
中的列舉值比對,如果成功就執行對應的陳述,否則就是執行 default
裏的陳述,就跟語法功能上描述的是一致的。
倒是可以稍微與 if
對照一下,在一些自由格式的 C-like 語言中,可以寫 if...else if...else
,因而形成瀑布式的流程,這樣的流程在外觀上,又有點像是 switch
可以做到的事,也就偶而有人會搞不清楚,什麼時候該用 if...else if...else
,什麼時候該用 switch
。
有時就會這麼建議,使用 switch
的話,值只會在一開始運算一次,然後逐一與 case
的列舉值比對,不用重複運算出值來;如果使用 if...else if...else
的話,那麼在每個 if
,條件式中的值都會運算一次,因此,若是 switch
跟 if...else if...else
都能解決的情況,使用 switch
會比較有效率。
就上面的 Switch
節點來看,確實值只會取出一次,之後就是 case
列舉值的比對,而在〈if 陳述〉中也看過 If
節點:
class If {
constructor(cond, trueStmt, falseStmt) {
this.cond = cond;
this.trueStmt = trueStmt;
this.falseStmt = falseStmt;
}
evaluate(context) {
if(this.cond.evaluate(context).value) {
return this.trueStmt.evaluate(context);
}
return this.falseStmt.evaluate(context);
}
}
每次 if
處,都會建立一個 If
節點,而每次 evaluate
,就會對 cond
也執行一次 evaluate
,若是 switch
跟 if...else if...else
都能解決的情況,使用 switch
會比較有效率,這種說法基本上是沒會的。
不過,若要就設計上而言, switch
跟 if...else if...else
的差別,在於 switch
是用在一些存在可列舉的情境,也就是值可以分類為特定幾個類型或狀態的情況,而 if...else if...else
,老實說,是用在狀況二分的情況,一個狀況是某條件成立,一個狀況是某條件不成立,而不是多分支,多分支只是程式碼排版上造成的錯覺。
如〈if 陳述〉中看到的,在被限制為不能使用 if...else if...else
時,也就是無法形成瀑布式流程時,就能突顯出實際上沒有多分支,仍舊是 if...else
罷了,不過奇怪的,大家對巢狀很敏感,然而對瀑布式卻沒那麼敏感,甚至經常視而不見,幾十條 if...else if...else
,明明就不正常,然而卻經常看到…XD