运维八一 运维八一
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)

运维八一

运维,运维!
首页
运维杂记
编程浅尝
周积跬步
专栏
生活
关于
收藏
  • 分类
  • 标签
  • 归档
Source (opens new window)
  • Go

    • 前言

    • Go基础知识

    • Go基本语法

    • 实战项目:简单web服务

    • 基本数据类型

    • 内置运算符

    • 分支和循环

      • if(分支结构)
      • switch(分支结构)
        • 2. switch(分支结构)
          • 2.1 switch 写法
          • 2.2 switch 执行次序
          • 2.3 switch 灵活性
          • 2.4 type switch
          • 2.5 switch 和 if 对比
          • 2.6 跳不出循环的 break
      • for(循环结构)
      • break和continue语句
    • 函数 function

    • 结构体 struct

    • 方法 method

    • 实战项目:跟踪函数调用链

    • 接口 interface

    • 并发 concurrency

    • 指针

    • 实战项目:实现轻量级线程池

    • 实战项目:实现TCP服务器

    • go常用包

    • Gin框架

    • go随记

  • Python

  • Shell

  • Java

  • Vue

  • 前端

  • 编程浅尝
  • Go
  • 分支和循环
lyndon
2022-06-07
目录

switch(分支结构)

# 2. switch(分支结构)

在一些执行分支较多的场景下,使用 switch 分支控制语句可以让代码更简洁,可读性更好。

# 2.1 switch 写法

Go 语言中 switch 语句的一般形式:

switch initStmt; expr {  // expr 表达式,initStmt 是一个可选的组成部分,可以通过短变量声明定义一些在 switch 语句中使用的临时变量。
    case expr1:
        // 执行分支1
    case expr2:
        // 执行分支2
    case expr3_1, expr3_2, expr3_3:
        // 执行分支3
    case expr4:
        // 执行分支4
    ... ...
    case exprN:
        // 执行分支N
    default: 	// 默认分支
        // 执行默认分支
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

switch 语句会用 expr 的求值结果与各个 case 中的表达式结果进行比较,如果发现匹配的 case,也就是 case 后面的表达式,或者表达式列表中任意一个表达式的求值结果与 expr 的求值结果相同,那么就会执行该 case 对应的代码分支,分支执行后,switch 语句也就结束了。如果所有 case 表达式都无法与 expr 匹配,那么程序就会执行 default 默认分支,并且结束 switch 语句。

# 2.2 switch 执行次序

Go 对各个 case 表达式进行求值并且与 switch 表达式(expr)进行比较的次序:

func case1() int {
    println("eval case1 expr")
    return 1
}

func case2_1() int {
    println("eval case2_1 expr")
    return 0 
}
func case2_2() int {
    println("eval case2_2 expr")
    return 2 
}

func case3() int {
    println("eval case3 expr")
    return 3
}

func switchexpr() int {
    println("eval switch expr")
    return 2
}

func main() {
    switch switchexpr() {
    case case1():
        println("exec case1")
    case case2_1(), case2_2():
        println("exec case2")
    case case3():
        println("exec case3")
    default:
        println("exec default")
    }
}

/*
eval switch expr
eval case1 expr
eval case2_1 expr
eval case2_2 expr
exec case2
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

Go 先对 switch expr 表达式进行求值,然后再按 case 语句的出现顺序,从上到下进行逐一求值。在带有表达式列表的 case 语句中,Go 会从左到右,对列表中的表达式进行求值,比如示例中的 case2_1 函数就执行于 case2_2 函数之前。

如果 switch 表达式匹配到了某个 case 表达式,那么程序就会执行这个 case 对应的代码分支,比如示例中的“exec case2”。这个分支后面的 case 表达式将不会再得到求值机会,比如示例不会执行 case3 函数。这里要注意一点,即便后面的 case 表达式求值后也能与 switch 表达式匹配上,Go 也不会继续去对这些表达式进行求值了。

无论 default 分支出现在什么位置,它都只会在所有 case 都没有匹配上的情况下才会被执行的。

优化小技巧:

考虑到 switch 语句是按照 case 出现的先后顺序对 case 表达式进行求值的,那么如果我们将匹配成功概率高的 case 表达式排在前面,就会有助于提升 switch 语句执行效率。这点对于 case 后面是表达式列表的语句同样有效,我们可以将匹配概率最高的表达式放在表达式列表的最左侧。

# 2.3 switch 灵活性

第一点:switch 语句各表达式的求值结果可以为各种类型值,只要它的类型支持比较操作就可以。

// 使用自定义结构体类型作为 switch 表达式类型的例子

type person struct {
    name string
    age  int
}

func main() {
    p := person{"tom", 13}
    switch p {
    case person{"tony", 33}:
        println("match tony")
    case person{"tom", 13}:
        println("match tom")
    case person{"lucy", 23}:
        println("match lucy")
    default:
        println("no match")
    }
}

// 当 switch 表达式的类型为布尔类型时,如果求值结果始终为 true,可以省略 switch 后面的表达式。
// 带有initStmt语句的switch语句
switch initStmt; {
    case bool_expr1:
    case bool_expr2:
    ... ...
}

// 没有initStmt语句的switch语句
switch {
    case bool_expr1:
    case bool_expr2:
    ... ...
}

// 注意,在带有 initStmt 的情况下,如果省略 switch 表达式,那么 initStmt 后面的分号不能省略,因为 initStmt 是一个语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

第二点:switch 语句支持声明临时变量。

switch 语句的 initStmt 可用来声明只在这个 switch 隐式代码块中使用的变量,这种就近声明的变量最大程度地缩小了变量的作用域。

第三点:case 语句支持表达式列表。

func checkWorkday(a int) {
    switch a {
    case 1, 2, 3, 4, 5:
        println("it is a work day")
    case 6, 7:
        println("it is a weekend day")
    default:
        println("are you live on earth")
    }
}
1
2
3
4
5
6
7
8
9
10

第四点:取消了默认执行下一个 case 代码逻辑的语义。

Swith 语句取消了默认执行下一个 case 代码逻辑的“非常规”语义,每个 case 对应的分支代码执行完后就结束 switch 语句。

如果在少数场景下,你需要执行下一个 case 的代码逻辑,你可以显式使用 Go 提供的关键字 ==fallthrough== 来实现,这也是 Go“显式”设计哲学的一个体现。

func case1() int {
    println("eval case1 expr")
    return 1
}

func case2() int {
    println("eval case2 expr")
    return 2
}

func switchexpr() int {
    println("eval switch expr")
    return 1
}

func main() {
    switch switchexpr() {
    case case1():
        println("exec case1")
        fallthrough
    case case2():
        println("exec case2")
        fallthrough
    default:
        println("exec default")
    }
}

/*
eval switch expr
eval case1 expr
exec case1
exec case2
exec default
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

注意:如果某个 case 语句已经是 switch 语句中的最后一个 case 了,并且它的后面也没有 default 分支了,那么这个 case 中就不能再使用 fallthrough,否则编译器就会报错。

# 2.4 type switch

“type switch”这是一种特殊的 switch 语句用法,支持求值结果为类型信息的表达式。

func main() {
    var x interface{} = 13
    switch x.(type) {
    case nil:
        println("x is nil")
    case int:
        println("the type of x is int")
    case string:
        println("the type of x is string")
    case bool:
        println("the type of x is string")
    default:
        println("don't support the type")
    }
}

/*
the type of x is int
*/

// 通过x.(type),除了可以获得变量 x 的动态类型信息之外,也能获得其动态类型对应的值信息
func main() {
    var x interface{} = 13
    switch v := x.(type) {	// v 存储的是变量 x 的动态类型对应的值信息
    case nil:
        println("v is nil")
    case int:
        println("the type of v is int, v =", v)
    case string:
        println("the type of v is string, v =", v)
    case bool:
        println("the type of v is bool, v =", v)
    default:
        println("don't support the type")
    }
}

/*
the type of v is int, v = 13
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

switch 关键字后面跟着的表达式为x.(type),这种表达式形式是 switch 语句专有的,而且也只能在 switch 语句中使用。

这个表达式中的 x 必须是一个接口类型变量,表达式的求值结果是这个接口类型变量对应的动态类型。

什么是一个接口类型的动态类型呢?简单解释一下。以上面的代码var x interface{} = 13为例,x 是一个接口类型变量,它的静态类型为interface{},如果我们将整型值 13 赋值给 x,x 这个接口变量的动态类型就为 int。

接着,case 关键字后面接的就不是普通意义上的表达式了,而是一个个具体的类型。这样,Go 就能使用变量 x 的动态类型与各个 case 中的类型进行匹配,之后的逻辑就都是一样的了。

如果在 switch 后面使用了某个特定的接口类型 I,那么 case 后面就只能使用实现了接口类型 I 的类型了,否则 Go 编译器会报错。

 type I interface {
      M()
  }

 type T struct {
  }

 func (T) M() {
 }

 func main() {
     var t T
     var i I = t
     switch i.(type) {
     case T:
         println("it is type T")
     case int:
         println("it is type int")
     case string:
         println("it is type string")
     }
 }

/*
19:2: impossible type switch case: i (type I) cannot have dynamic type int (missing M method)
21:2: impossible type switch case: i (type I) cannot have dynamic type string (missing M method)
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

在 type switch 中使用了自定义的接口类型 I。那么,理论上所有 case 后面的类型都只能是实现了接口 I 的类型。但在这段代码中,只有类型 T 实现了接口类型 I,Go 原生类型 int 与 string 都没有实现接口 I,于是在编译上述代码时,编译器会报出错误信息。

# 2.5 switch 和 if 对比

// readByExt 函数会根据传入的文件扩展名输出不同的日志

//使用 if 语句进行分支控制:
func readByExt(ext string) {
    if ext == "json" {
        println("read json file")
    } else if ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "gif" {
        println("read image file")
    } else if ext == "txt" || ext == "md" {
        println("read text file")
    } else if ext == "yml" || ext == "yaml" {
        println("read yaml file")
    } else if ext == "ini" {
        println("read ini file")
    } else {
        println("unsupported file extension:", ext)
    }
}

// 使用 switch 语句进行分支控制:
func readByExtBySwitch(ext string) {
    switch ext {
    case "json":
        println("read json file")
    case "jpg", "jpeg", "png", "gif":
        println("read image file")
    case "txt", "md":
        println("read text file")
    case "yml", "yaml":
        println("read yaml file")
    case "ini":
        println("read ini file")
    default:
        println("unsupported file extension:", ext)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

使用 switch 语句可方便地对大量的值进行条件判断:

package main

import "fmt"

func main() {
   score := "B"
   switch score {
   case "A":
      fmt.Printf("优")
   case "B":
      fmt.Println("良")
   case "C":
      fmt.Println("及格")
   case "D":
      fmt.Println("不及格")
   }
}

/*
良
*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.6 跳不出循环的 break

func main() {
    var sl = []int{5, 19, 6, 3, 8, 12}
    var firstEven int = -1

    // find first even number of the interger slice
    for i := 0; i < len(sl); i++ {
        switch sl[i] % 2 {
        case 0:
            firstEven = sl[i]
            break
        case 1:
            // do nothing
        }        
    }         
    println(firstEven) 

}

/*
12
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

切片中的第一个偶数是 6,而输出的结果却成了切片的最后一个偶数 12。出现这种结果的原因:

Go 中 break 语句与 switch 分支结合使用会出现一个“小坑”。Go 语言规范中明确规定,不带 label 的 break 语句中断执行并跳出的,是同一函数内 break 语句所在的最内层的 for、switch 或 select。所以,上面这个例子的 break 语句实际上只跳出了 switch 语句,并没有跳出外层的 for 循环,这也就是程序未按我们预期执行的原因。

使用带 label 的 break改进代码:

func main() {
    var sl = []int{5, 19, 6, 3, 8, 12}
    var firstEven int = -1

    // find first even number of the interger slice

loop:
    for i := 0; i < len(sl); i++ {
        switch sl[i] % 2 {
        case 0:
            firstEven = sl[i]
            break loop
        case 1:
            // do nothing
        }
    }
    println(firstEven) // 6
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

定义了一个 label:loop,这个 label 附在 for 循环的外面,指代 for 循环的执行。当代码执行到“break loop”时,程序将停止 label loop 所指代的 for 循环的执行。

和 switch 语句一样能阻拦 break 跳出的还有一个语句,那就是 select。

上次更新: 2022/06/12, 15:48:09
if(分支结构)
for(循环结构)

← if(分支结构) for(循环结构)→

最近更新
01
ctr和crictl显示镜像不一致
03-13
02
alpine镜像集成常用数据库客户端
03-13
03
create-cluster
02-26
更多文章>
Theme by Vdoing | Copyright © 2015-2024 op81.com
苏ICP备18041258号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式