如何在 Go 中编写 Switch 语句
介绍
条件语句使程序员有能力指导他们的程序在某个条件为真时采取某些行动,在条件为假时采取另一种行动。经常,我们想把一些变量与多个可能的值进行比较,在每种情况下采取不同的行动。仅仅使用if语句就可以做到这一点。然而,编写软件不仅是为了让事情顺利进行,也是为了向未来的自己和其他开发者传达你的意图。switch是一个替代性的条件语句,对于传达你的 Go 程序在遇到不同选项时采取的行动很有用。
我们可以用 switch 语句编写的所有内容也可以用if语句编写。在本教程中,我们将看几个例子,看看 switch 语句能做什么,它所取代的if语句,以及它最合适的应用场合。
Switch 语句的结构
Switch 通常用于描述当一个变量被分配到特定值时程序所采取的行动。下面的例子演示了我们如何使用 if 语句来完成这个任务。
package mainimport "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的冰激凌口味。然后我们使用一个for loop来迭代它们。我们使用三个if语句来打印不同的信息,表明对不同冰淇淋口味的偏好。每个if语句必须使用continue语句来停止for循环的执行,这样就不会在最后打印出首选冰淇淋口味的默认信息。
当我们添加新的偏好时,我们必须不断添加if语句来处理新的情况。重复的信息,如 “香草”和 “巧克力”的情况,必须有重复的if语句。对于我们代码的未来读者(包括我们自己)来说,if语句的重复性掩盖了它们所做的重要部分—将变量与多个值进行比较并采取不同的行动。另外,我们的回退信息与条件语句分开,使得它看起来不相关。转换器 “语句可以帮助我们更好地组织这个逻辑。
switch 语句以 switch 关键字开始,在其最基本的形式下,后面是一些要进行比较的变量。之后是一对大括号({}),其中可以出现多个case 子句。case 子句描述了当提供给 switch 语句的变量等于 case 子句所引用的值时,Go 程序应该采取的行动。下面的例子将先前的例子转换为使用一个switch而不是多个if语句:
package mainimport "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 mainimport ("fmt""math/rand""time")func main() {rand.Seed(time.Now().UnixNano())target := rand.Intn(100)for {var guess intfmt.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: 10Too low!Enter a guess: 15Too low!Enter a guess: 18Too high!Enter a guess: 17You win!
我们的猜谜游戏需要一个随机数来比较猜测的结果,所以我们使用math/rand包中的rand.Intn函数。为了确保我们每次玩游戏都能得到不同的target值,我们使用rand.Seed来根据当前时间随机化随机数发生器。rand.Intn的参数100将给我们一个0-100范围内的数字。然后我们使用for循环来开始收集玩家的猜测。
fmt.Scanf函数为我们提供了一种方法来读取用户的输入到我们选择的变量中。它接受一个格式化的字符串动词,将用户的输入转换为我们期望的类型。这里的%d意味着我们期望一个 int,我们传递 guess 变量的地址,这样 fmt.Scanf 就能够设置该变量。在处理任何解析错误之后,我们使用两个if语句来比较用户的猜测和target值。它们返回的string和bool一起控制显示给玩家的信息,以及游戏是否会退出。
这些 if 语句掩盖了一个事实,即变量被比较的数值范围都有某种联系。一眼就能看出我们是否遗漏了该范围的某些部分,这也是很困难的。下一个例子重构了前面的例子,用switch语句代替:
package mainimport ("fmt""math/rand")func main() {target := rand.Intn(100)for {var guess intfmt.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: 25Too low!Enter a guess: 28Too high!Enter a guess: 27You win!
在这个版本的猜谜游戏中,我们用一个switch语句代替了if语句块。我们省略了switch的表达式参数,因为我们只对使用switch来收集条件语句感兴趣。每个case子句包含一个不同的表达式,将guess与target进行比较。与第一次用switch代替if语句类似,我们不再需要continue语句,因为只有一个case子句会被执行。最后,default子句处理guess == target的情况,因为我们已经用另外两个case子句覆盖了所有其他可能的值。
在我们目前看到的例子中,正好有一个 case 语句将被执行。偶尔,你可能希望结合多个case子句的行为。switch语句提供了另一个实现这种行为的关键字。
Fallthrough
有时你想重复使用另一个 case 子句包含的代码。在这种情况下,可以使用 fallthrough 关键字要求 Go 运行下一个 case 子句的主体。下面这个例子修改了我们之前的冰淇淋口味的例子,以更准确地反映我们对草莓冰淇淋的热情:
package mainimport "fmt"func main() {flavors := []string{"chocolate", "vanilla", "strawberry", "banana"}for _, flav := range flavors {switch flav {case "strawberry":fmt.Println(flav, "is my favorite!")fallthroughcase "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语句重写它—你会发现当需要考虑其他值时,它将更容易重写。
如果你想了解更多关于 Go 编程语言的信息,请查看整个How To Code in Go 系列
