「每週譯 Go」如何在 Go 中編寫 Switch 語句


介紹

條件語句(點擊查看往期推文) 使程序員有能力指導他們的程序在某個條件爲真時採取某些行動,在條件爲假時採取另一種行動。經常,我們想把一些 變量 [1] 與多個可能的值進行比較,在每種情況下采取不同的行動。僅僅使用if語句 [2] 就可以做到這一點。然而,編寫軟件不僅是爲了讓事情順利進行,也是爲了向未來的自己和其他開發者傳達你的意圖。switch是一個替代性的條件語句,對於傳達你的 Go 程序在遇到不同選項時採取的行動很有用。

我們可以用 switch 語句編寫的所有內容也可以用if語句編寫。在本教程中,我們將看幾個例子,看看 switch 語句能做什麼,它所取代的if語句,以及它最合適的應用場合。

Switch 語句的結構

Switch 通常用於描述當一個變量被分配到特定值時程序所採取的行動。下面的例子演示了我們如何使用 if 語句來完成這個任務。

package main

import "fmt"

func main() {
 flavors := []string{"chocolate""vanilla""strawberry""banana"}

 for _, flav := range flavors {
  if flav == "strawberry" {
   fmt.Println(flav, "is my favorite!")
   continue
  }

  if flav == "vanilla" {
   fmt.Println(flav, "is great!")
   continue
  }

  if flav == "chocolate" {
   fmt.Println(flav, "is great!")
   continue
  }

  fmt.Println("I've never tried", flav, "before")
 }
}

這將輸出如下信息:

chocolate is great!
vanilla is great!
strawberry is my favorite!
I've never tried banana before

main中,我們定義了一個 slice[3] 的冰激凌口味。然後我們使用一個for loop(下週一推文會講到)來迭代它們。我們使用三個if語句來打印不同的信息,表明對不同冰淇淋口味的偏好。每個if語句必須使用continue語句來停止for循環的執行,這樣就不會在最後打印出首選冰淇淋口味的默認信息。

當我們添加新的偏好時,我們必須不斷添加if語句來處理新的情況。重複的信息,如 "香草" 和 "巧克力" 的情況,必須有重複的if語句。對於我們代碼的未來讀者(包括我們自己)來說,if語句的重複性掩蓋了它們所做的重要部分 -- 將變量與多個值進行比較並採取不同的行動。另外,我們的回退信息與條件語句分開,使得它看起來不相關。轉換器 " 語句可以幫助我們更好地組織這個邏輯。

switch 語句以 switch 關鍵字開始,在其最基本的形式下,後面是一些要進行比較的變量。之後是一對大括號({}),其中可以出現多個 case 子句。case 子句描述了當提供給 switch 語句的變量等於 case 子句所引用的值時,Go 程序應該採取的行動。下面的例子將先前的例子轉換爲使用一個switch而不是多個if語句:

package main

import "fmt"

func main() {
 flavors := []string{"chocolate""vanilla""strawberry""banana"}

 for _, flav := range flavors {
  switch flav {
  case "strawberry":
   fmt.Println(flav, "is my favorite!")
  case "vanilla""chocolate":
   fmt.Println(flav, "is great!")
  default:
   fmt.Println("I've never tried", flav, "before")
  }
 }
}

輸出與之前相同:

chocolate is great!
vanilla is great!
strawberry is my favorite!
I've never tried banana before

我們再次在main中定義了一片冰淇淋的口味,並使用range語句來遍歷每個口味。但是這一次,我們使用了一個switch語句來檢查flav變量。我們使用兩個case'子句來表示偏好。我們不再需要繼續'語句,因爲只有一個case子句將被switch語句執行。我們還可以將 "巧克力" 和 "香草" 條件的重複邏輯結合起來,在 case子句的聲明中用逗號將其分開。default子句是我們的萬能子句。它將對我們在 switch 語句中沒有考慮到的任何口味運行。在這種情況下,"香蕉" 將導致 default 的執行,打印出 "I've never tried banana before" 的信息。

這種簡化形式的switch語句解決了它們最常見的用途:將一個變量與多個替代品進行比較。它還爲我們提供了便利,當我們想對多個不同的值採取相同的行動,以及在沒有滿足所列的條件時,通過使用所提供的default關鍵字採取一些其他行動。

當這種簡化的switch形式被證明太有侷限性時,我們可以使用一種更通用的switch語句形式。

通常的 Switch 語句

switch語句對於將更復雜的條件集合在一起以顯示它們之間有某種聯繫是很有用的。這在將某些變量與一定範圍的值進行比較時最常用,而不是像前面的例子中的特定值。下面的例子使用if語句實現了一個猜謎遊戲,可以從switch語句中受益:

package main

import (
 "fmt"
 "math/rand"
 "time"
)

func main() {
 rand.Seed(time.Now().UnixNano())
 target := rand.Intn(100)

 for {
  var guess int
  fmt.Print("Enter a guess: ")
  _, err := fmt.Scanf("%d"&guess)
  if err != nil {
   fmt.Println("Invalid guess: err:", err)
   continue
  }

  if guess > target {
   fmt.Println("Too high!")
   continue
  }

  if guess < target {
   fmt.Println("Too low!")
   continue
  }

  fmt.Println("You win!")
  break
 }
}

輸出將取決於所選擇的隨機數和你玩遊戲的程度。下面是一個例子會話的輸出:

Enter a guess: 10
Too low!
Enter a guess: 15
Too low!
Enter a guess: 18
Too high!
Enter a guess: 17
You win!

我們的猜謎遊戲需要一個隨機數來比較猜測的結果,所以我們使用math/rand包中的rand.Intn函數。爲了確保我們每次玩遊戲都能得到不同的target值,我們使用rand.Seed來根據當前時間隨機化隨機數發生器。rand.Intn的參數100將給我們一個 0-100 範圍內的數字。然後我們使用for循環來開始收集玩家的猜測。

fmt.Scanf函數爲我們提供了一種方法來讀取用戶的輸入到我們選擇的變量中。它接受一個格式化的字符串動詞,將用戶的輸入轉換爲我們期望的類型。這裏的%d意味着我們期望一個 int,我們傳遞 guess 變量的地址,這樣 fmt.Scanf 就能夠設置該變量。在 處理任何解析錯誤(點擊查看往期推文)之後,我們使用兩個if語句來比較用戶的猜測和target值。它們返回的stringbool一起控制顯示給玩家的信息,以及遊戲是否會退出。

這些 if 語句掩蓋了一個事實,即變量被比較的數值範圍都有某種聯繫。一眼就能看出我們是否遺漏了該範圍的某些部分,這也是很困難的。下一個例子重構了前面的例子,用switch語句代替:

package main

import (
 "fmt"
 "math/rand"
)

func main() {
 target := rand.Intn(100)

 for {
  var guess int
  fmt.Print("Enter a guess: ")
  _, err := fmt.Scanf("%d"&guess)
  if err != nil {
   fmt.Println("Invalid guess: err:", err)
   continue
  }

  switch {
  case guess > target:
   fmt.Println("Too high!")
  case guess < target:
   fmt.Println("Too low!")
  default:
   fmt.Println("You win!")
   return
  }
 }
}

這將產生類似以下的輸出:

Enter a guess: 25
Too low!
Enter a guess: 28
Too high!
Enter a guess: 27
You win!

在這個版本的猜謎遊戲中,我們用一個switch語句代替了if語句塊。我們省略了switch的表達式參數,因爲我們只對使用switch來收集條件語句感興趣。每個case子句包含一個不同的表達式,將guesstarget進行比較。與第一次用switch代替if語句類似,我們不再需要continue語句,因爲只有一個case子句會被執行。最後,default子句處理guess == target的情況,因爲我們已經用另外兩個case子句覆蓋了所有其他可能的值。

在我們目前看到的例子中,正好有一個 case 語句將被執行。偶爾,你可能希望結合多個case子句的行爲。switch語句提供了另一個實現這種行爲的關鍵字。

Fallthrough

有時你想重複使用另一個 case 子句包含的代碼。在這種情況下,可以使用 fallthrough 關鍵字要求 Go 運行下一個 case 子句的主體。下面這個例子修改了我們之前的冰淇淋口味的例子,以更準確地反映我們對草莓冰淇淋的熱情:

package main

import "fmt"

func main() {
 flavors := []string{"chocolate""vanilla""strawberry""banana"}

 for _, flav := range flavors {
  switch flav {
  case "strawberry":
   fmt.Println(flav, "is my favorite!")
   fallthrough
  case "vanilla""chocolate":
   fmt.Println(flav, "is great!")
  default:
   fmt.Println("I've never tried", flav, "before")
  }
 }
}

將得到如下輸出:

chocolate is great!
vanilla is great!
strawberry is my favorite!
strawberry is great!
I've never tried banana before

正如我們之前看到的,我們定義了一個 string 片段來表示口味,並使用 for 循環來迭代。這裏的 switch 語句與我們之前看到的語句相同,但是在 case 子句的末尾添加了 fallthrough 關鍵字,即 strawberry。這將使 Go 運行case "strawberry":的主體,首先打印出字符串strawberry is my favorite!。當它遇到fallthrough時,它將運行下一個case子句的主體。這將導致case "vanilla", "chocolate":的主體運行,打印出strawberry is great!

Go 開發人員不經常使用fallthrough關鍵字。通常情況下,通過使用fallthrough實現的代碼重用,可以通過定義一個具有公共代碼的函數來更好地獲得。由於這些原因,一般不鼓勵使用fallthrough

總結

switch語句幫助我們向閱讀代碼的其他開發者傳達出彼此有某種聯繫。使我們在將來添加新的情況時更容易添加不同的行爲,並有可能確保任何忘記的事情也能通過default子句得到正確處理。下次你發現自己寫的多個if語句都涉及同一個變量時,試着用switch語句重寫它 -- 你會發現當需要考慮其他值時,它將更容易重寫。

相關鏈接:

[1]https://gocn.github.io/How-To-Code-in-Go/docs/11-How_To_Use_Variables_and_Constants_in_Go/#%E7%90%86%E8%A7%A3%E5%8F%98%E9%87%8F

[2]https://gocn.github.io/How-To-Code-in-Go/docs/23-How_To_Write_Conditional_Statements_in_Go/#if-%E8%AF%AD%E5%8F%A5

[3]https://gocn.github.io/How-To-Code-in-Go/docs/12-How_To_Convert_Data_Types_in_Go

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Lh0InvVt05PMmVoNtUjCNg