• Go学习笔记 -- 流程控制 for、switch、select


    For

    Go 中的 For 循环可以分为三种形式:

    for [( init; condition; increment )]{}

    示例

    for i := 0; i < 10; i++ {
    	fmt.Println(i)
    }
    
    • 1
    • 2
    • 3

    for [condition]{}

    直接判断条件是否成立,相当于 while(condition)

    i := 0
    for i < 10 {
    	fmt.Println(i)
    	i++
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当然我们可以省略关系表达式,那样就变成了一个无限循环 while(true)

    for {
    	fmt.Println("circle")
    }
    
    • 1
    • 2
    • 3

    for [Range]{}

    for range 是比较常用的循环手段,我们可以通过 range 来获取集合对象中元素的下标和值
    其中 i 为元素下标,v 为元素值

    list := []string{"one", "two", "three"}
    for i, v := range list {
    	fmt.Println(i, v)
    }
    -----------------------------
    0 one
    1 two
    2 three
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果我们在 range 的左侧只有一个接收值,那么这个值表示元素下标

    list := []string{"one", "two", "three"}
    for i := range list {
    	fmt.Println(i)
    }
    -----------------------------
    0
    1
    2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意 Range 循环的对象是引用类型还是值类型!!!

    如下代码:
    一个是 数组 list 一个是 切片 list_slice ,拥有同样的元素。
    我们从第二个元素开始,将当前元素加上前一个元素的值作为新的值。
    最后查看打印结果,两者却不一样,切片打印结果(一直累加)符合我们的预期。

    list := [...]int{1, 1, 1, 1, 1}
    for i, v := range list {
    	if i+1 < len(list) {
    		list[i+1] += v
    	}
    }
    fmt.Println(list)
    
    list_slice := []int{1, 1, 1, 1, 1}
    for i, v := range list_slice {
    	if i+1 < len(list_slice) {
    		list_slice[i+1] += v
    	}
    }
    fmt.Println(list_slice)
    -----------------------------------
    [1 2 2 2 2]
    [1 2 3 4 5]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18


    原因如下:
    首先,切片是引用类型的,数组是值类型的。
    其次,循环伊始的 Range 表达式,只会在 for 语句执行时被求值一次,即,当运行 for i, v := range list 这一句之后,v 的值就已经被确认了。而且在数组循环中,v 的值其实只是一个副本,对原来值的修改不会影响到 v 的值。而对于引用类型的切片来说,其在循环时使用的副本实际上是指针的副本,副本指针指向的内存地址和原指针指向的内存地址是一样的,我们对 v 的修改就是对这个内存地址的值做的修改。

    当然我们可以不在 Range 阶段求值,那样就会避免求值产生副本的问题。

    list := [...]int{1, 1, 1, 1, 1}
    for i := range list {
    	if i+1 < len(list) {
    		list[i+1] += list[i]
    	}
    }
    fmt.Println(list)
    -----------------------------------
    [1 2 3 4 5]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Switch

    在 switch 语句中,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。 switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

    通过 switch 的值和 case 的值是否一致,判断逻辑分支

      case 表达式中,可以放多个情况值。
      Go 语言中每个分支的最后默认都是有一个 break 的,使用 fallthrough 可以在走到当前分支后强制执行下一个 case

    var ilist = []uint8{0, 1, 2, 3}
    switch ilist[0] {
    case 0:
    	fmt.Println("000")
    case 1:
    	fmt.Println("111222")
    	fallthrough
    default:
    	fmt.Println("default")
    }
    fmt.Println("--------分割线--------")
    //使用 fallthrough 可以在走到当前分支后强制执行下一个 case 
    switch ilist[2] {
    case 0:
    	fmt.Println("000")
    case 1 , 2:
    	fmt.Println("111222")
    	fallthrough
    default:
    	fmt.Println("default")
    }
    ----------------------------------
    000
    --------分割线--------
    111222
    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

    省略条件表达式

    也可以省略条件表达式,当作 if… else… 使用

    var i int = 1
    switch {
    case i > 0 && i < 2:
    	fmt.Println("1")
    case i > 1 && i < 3:
    	fmt.Println("2")
    default:
    	fmt.Println("default")
    }
    ----------------------------------
    1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Type Switch 判断接口类型

    type Test struct {
    	name string
    }
    
    func main() {
    	var test1 = Test{"测试"}
    	switch i := interface{}(test1).(type) {
    	case nil:
    		fmt.Printf(" x 为空")
    	case int:
    		fmt.Printf("x 是 int 型")
    	case bool, string:
    		fmt.Printf("x 是 bool 或 string 型")
    	default:
    		fmt.Printf("其他类型 %T", i)
    	}
    }
    --------------------------------
    其他类型 main.Test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Select

    • select 是类似于 switch 的存在,但是他的分支需要绑定到通信操作上,select 中的 case 都是通信操作,要么接受,要么发送。
    • 在一开始所有 channel 表达式都会被求值,如果其中任意一个通信可以进行,那么他就会执行,其他的会被忽略。
    • 如果有多个case都可以运行,select 会随机公平地选出一个执行。其他不会执行。
    • 如果没有分支可执行,但有 default 子句,则执行 default 语句。
    • 如果没有分支可执行,且没有 default 字句,select 将阻塞,直到某个通信可以运行。

      如下代码,如果我们不屏蔽 default 分支,正常情况下最终将会打印十次 none value 结果,因为我们在执行 select 的时候,我们创建的三个通道里面都还没有值,过100 ms 之后我们才会放值,select 将走 default 分支。
      如果我们屏蔽掉 defalut 分支,打印结果如下,每一次的执行结果都可能不一样,哪一个通道中能先获取到值,就会执行对应分支的逻辑。

    func goFunc(i int, ch *chan int) {
    	time.Sleep(time.Millisecond * 100)
    	*ch <- i
    }
    
    func main() {
    
    	ch0 := make(chan int, 3)
    	ch1 := make(chan int, 3)
    	ch2 := make(chan int, 3)
    
    	for i := 0; i < 10; i++ {
    
    		ch0 = make(chan int, 3)
    		ch1 = make(chan int, 3)
    		ch2 = make(chan int, 3)
    
    		go goFunc(0, &ch0)
    		go goFunc(1, &ch1)
    		go goFunc(2, &ch2)
    
    		select {
    		case v := <-ch0:
    			fmt.Printf("ch0 current value is %d\n", v)
    		case v := <-ch1:
    			fmt.Printf("ch1 current value is %d\n", v)
    		case v := <-ch2:
    			fmt.Printf("ch2 current value is %d\n", v)
    			// default:
    			// 	fmt.Printf("none value\n")
    		}
    	}
    
    	//等待一段时间
    	time.Sleep(time.Millisecond * 5000)
    }
    ----------------------
    ch1 current value is 1
    ch1 current value is 1
    ch0 current value is 0
    ch1 current value is 1
    ch2 current value is 2
    ch0 current value is 0
    ch2 current value is 2
    ch0 current value is 0
    ch1 current value is 1
    ch2 current value is 2
    
    • 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
    • 45
    • 46
    • 47

    同理,我们也可以通过校验通道是否可以写入值来控制分支。修改一下上面的代码,将其中一个分支修改为向通道中写值
    执行到 select 时,所有通道中都没有值,但是 ch0 可以写入,所以会走第一个分支,打印结果全是 ch0 set value 9

    func goFunc(i int, ch *chan int) {...}
    
    func main() {
    ...
    		select {
    		case ch0 <- 9:
    			fmt.Printf("ch0 set value %d\n", <-ch0)
    ...
    		}
    	}
    ...
    }
    ----------------------
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    ch0 set value 9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    司徒理财:10.26周四黄金走势分析,黄金操作策略,谨慎追多
    虚拟机配置spark
    VB.net下使用开源免费三维控件Anycad的应用实例分享
    XML|DTD声明
    please choose a certificate and try again.(-5)报错怎么解决
    简述LSM-Tree
    INS/GPS组合导航类型简介
    【前端】NodeJS:包管理工具
    数学建模学习(88):飞蛾扑火算法(WFO)寻优
    python特别篇—github基本操作手册
  • 原文地址:https://blog.csdn.net/qq_40096897/article/details/128177595