跳转到主要内容

介绍


条件语句使程序员能够指导他们的程序在条件为真时采取某种行动,而在条件为假时采取另一种行动。通常,我们希望将某个变量与多个可能的值进行比较,在每种情况下采取不同的行动。可以单独使用 if 语句来完成这项工作。然而,编写软件不仅是为了让事情顺利进行,而且还要将你的意图传达给你未来的自己和其他开发人员。 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")
    }
}

这将生成以下输出:

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


在 main 中,我们定义了一片冰淇淋口味。然后我们使用 for 循环遍历它们。我们使用三个 if 语句打印出不同的消息,表明对不同冰淇淋口味的偏好。每个 if 语句都必须使用 continue 语句来停止 for 循环的执行,以便不会为首选冰淇淋口味打印末尾的默认消息。

当我们添加新的冰淇淋偏好时,我们必须不断添加 if 语句来处理新的情况。重复的消息,如“香草”和“巧克力”的情况,必须有重复的 if 语句。对于我们代码的未来读者(包括我们自己)来说,if 语句的重复性掩盖了他们正在做的事情的重要部分——将变量与多个值进行比较并采取不同的操作。此外,我们的后备消息与条件语句分开,使其看起来不相关。 switch 语句可以帮助我们更好地组织这个逻辑。

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")
        }
    }
}

输出与之前相同:

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


我们再次在 main 中定义了一片冰淇淋口味,并使用 range 语句来迭代每种口味。然而,这一次,我们使用了一个 switch 语句来检查 flav 变量。我们使用两个 case 子句来表示偏好。我们不再需要 continue 语句,因为 switch 语句只会执行一个 case 子句。我们还可以通过在 case 子句的声明中用逗号分隔“chocolate”和“vanilla”条件句的重复逻辑。默认子句用作我们的包罗万象的子句。它将针对我们在 switch 语句主体中未考虑的任何风格运行。在这种情况下,“banana”将导致默认执行,打印我以前从未尝试过香蕉的消息。

这种简化形式的 switch 语句解决了它们最常见的用途:将一个变量与多个备选方案进行比较。它还为我们提供了便利,我们希望通过使用提供的默认关键字对多个不同的值采取相同的操作以及在没有满足列出的条件时采取一些其他操作。

当这种简化形式的 switch 被证明过于局限时,我们可以使用更通用的 switch 语句形式。

通用切换语句


switch 语句对于将更复杂的条件集合进行分组以显示它们以某种方式相关是有用的。这在将某个变量与一系列值进行比较时最常用,而不是前面示例中的特定值。以下示例使用可以从 switch 语句中受益的 if 语句实现猜谜游戏:

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
    }
}

输出将根据选择的随机数和您玩游戏的程度而有所不同。以下是一个示例会话的输出:

Output
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 函数。为了确保我们在每次玩游戏时获得不同的目标值,我们使用 rand.Seed 根据当前时间随机化随机数生成器。 rand.Intn 的参数 100 将给我们一个 0-100 范围内的数字。然后我们使用 for 循环开始收集玩家的猜测。

fmt.Scanf 函数为我们提供了一种将用户输入读入我们选择的变量的方法。它采用格式字符串动词,将用户的输入转换为我们期望的类型。这里的 %d 意味着我们期望一个 int,并且我们传递了guess 变量的地址,以便 fmt.Scanf 能够设置该变量。在处理任何解析错误之后,我们使用两个 if 语句将用户的猜测与目标值进行比较。他们返回的字符串与 bool 一起控制向玩家显示的消息以及游戏是否退出。

这些 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
        }
    }
}

这将生成类似于以下内容的输出:

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


在这个版本的猜谜游戏中,我们将 if 语句块替换为 switch 语句。我们省略了 switch 的表达式参数,因为我们只对使用 switch 来收集条件感兴趣。每个 case 子句都包含一个不同的表达式,将猜测与目标进行比较。与我们第一次用 switch 替换 if 语句类似,我们不再需要 continue 语句,因为只会执行一个 case 子句。最后,default 子句处理guess == target 的情况,因为我们已经用其他两个case 子句覆盖了所有其他可能的值。

在我们目前看到的例子中,只有一个 case 语句会被执行。有时,您可能希望组合多个 case 子句的行为。 switch 语句为实现此行为提供了另一个关键字。

穿越(Fallthrough)


有时您会想要重用另一个 case 子句包含的代码。在这些情况下,可以让 Go 运行使用 fallthrough 关键字列出的下一个 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")
        }
    }
}

我们将看到这个输出:

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


正如我们之前看到的,我们定义了一个字符串切片来表示风味,并使用 for 循环遍历它。这里的 switch 语句与我们之前看到的相同,但在“草莓”的 case 子句末尾添加了 fallthrough 关键字。这将导致 Go 运行 case "strawberry": 的主体,首先打印出字符串草莓是我最喜欢的!当它遇到 fallthrough 时,它将运行下一个 case 子句的主体。这将导致案例“香草”、“巧克力”:运行,打印草莓很棒!

go 开发人员不经常使用 fallthrough 关键字。通常,使用fallthrough实现的代码重用,可以通过用普通代码定义一个函数来更好的获得。由于这些原因,通常不鼓励使用 fallthrough。

结论


switch 语句帮助我们向阅读我们代码的其他开发人员传达一组比较在某种程度上相互关联。当将来添加新案例时,它们可以更容易地添加不同的行为,并且可以确保我们忘记的任何内容都得到正确处理以及使用默认子句。下次当您发现自己编写多个涉及相同变量的 if 语句时,请尝试使用 switch 语句重写它——当您需要考虑其他替代值时,您会发现更容易重写。

文章链接