search.png
关于我
menu.png
go 流程控制

if ... else if ... else 语句

golang的if 条件中不需要加括号,并且if支持写声明语句

import "fmt"

func testIf1(c int) {
    a := 1
    // if 可以写声明语句
    if b := 4; a < c && c > b {
        fmt.Println("c")
    } else if a = 3; a == c {
        fmt.Println("a")
    } else {
        fmt.Println("b")
    }
}

func main() {
    testIf1(123)
    testIf1(3)
    testIf1(1)
}


switch语句

go的switch语句不用加break,会自动退出

func testSwitch1(score int) {
    switch score {
    case 1:
        fmt.Println("E")
    case 2:
        fmt.Println("D")
    case 3:
        fmt.Println("C")
    case 4:
        fmt.Println("B")
    case 5:
        fmt.Println("A")
    }
}

也可以显式break退出:

func testSwitch2(score int) {
    switch score {
    case 1:
        fmt.Println("E")
    case 2:
        fmt.Println("is 2")
        break
        fmt.Println("D")
    case 3:
        fmt.Println("C")
    case 4:
        fmt.Println("B")
    case 5:
        fmt.Println("A")
    }
}

可以使用fallthrough到下一个case:

func testSwitch3(score int) {
    switch score {
    case 1:
        fmt.Println("E")
    // case 可以是多个值
    case 2,6:
        fmt.Println("D")
        //if score == 6 {
        // fallthrough 不能写在新的变量环境里,只能至于case下面,这个if语句语法报错
        // fallthrough
        //}
        // fallthrough 跳到下一个case
        fallthrough
    case 3:
        fmt.Println("C")
    case 4:
        fmt.Println("B")
    default:
        // default 不能执行fallthrough 语法报错
        //fallthrough
        fmt.Println("default")
    case 5:
        fmt.Println("A")
        // 最后一个case 不能执行fallthrough 语法报错
        //fallthrough
    }
}

可以省略变量,直接写条件语句

func testSwitch4(score int) {
    switch {
        case score > 3:
            fmt.Println("more than C")
        case score < 2:
            fmt.Println("is e")
    }
}

switch 支持写声明语句

func testSwitch5(t interface{}) {
    switch typ := t.(type) {
    case nil:
        fmt.Println("is nil")
    case int:
        fmt.Printf("is int %d\n", typ)
    case []int:
        fmt.Printf("is []int %v\n", typ)
    case func(int) int:
        fmt.Println("is func(int) int")
    default:
        fmt.Println("is other thing")
    }
}

for 循环

for init; condition; post { }
for condition { }
for { }
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。

for语句执行过程如下:
①先对表达式 init 赋初值;
②判别赋值表达式 init 是否满足给定 condition 条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

func testFor1() {
    a := [...]int{1,2,3,4,5}

    // c中常见的循环遍历写法
    for i,l := 0,len(a); i< l; i++ {
        fmt.Printf("%d-%d\n", i, a[i])
    }

    // for range 
    // for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。
    for i,v := range a {
        fmt.Printf("%d-%d\n", i, v)
    }

    // 遍历map
    m := map[string]string {
        "a":"aa",
        "b":"bb",
    }
    for k,v := range m {
        fmt.Printf("%s-%s\n", k, v)
    }

    // while
    b := 1
    for b < 10 {
        fmt.Println(b)
        b ++
    }

    // while(true)
    c := 1
    for {
        fmt.Println(c)
        c ++
        if c == 10 {
            break
        }
    }
}

需要注意for和range使用,range在循环时会自动进行值的拷贝,所以直接修改其中的值,是改不了的:

func testFor2() {
    s := []int{1, 2, 3, 4, 5}

    for i, v := range s { // 复制 struct slice { pointer, len, cap }。

        if i == 0 {
            s = s[:3]  // 对 slice 的修改,不会影响 range。
            s[2] = 100 // 对底层数据的修改。
        }

        println(i, v)
    }
}

func testArr3() {
    type A struct {
        a string
    }
    arr := [...]A{
        {a: "1"},
        {a: "2"},
    }
    fmt.Println(arr)
    for i, v := range arr {
        // range 是值传递,因此无法直接修改对象的值,对比地址的结果是false
        fmt.Println(&v == &arr[i])
        v.a = "3"
    }
    fmt.Println(arr)

    // 通过索引修改没有问题
    for i, _ := range arr {
        arr[i].a = "3"
    }
    fmt.Println(arr)

    // 指针数组,遍历的是指针,如此修改值生效
    arr2 := [...]*A{
        {a: "1"},
        {a: "2"},
    }
    fmt.Println(*arr2[0], *arr2[1])
    for i, v := range arr2 {
        fmt.Println(v == arr2[i])
        v.a = "3"
    }
    fmt.Println(*arr2[0], *arr2[1])
}

testArr3的结果:

[{1} {2}]
false
false
[{1} {2}]
[{3} {3}]
{1} {2}
true
true
{3} {3}

查看下例,m中A的值会被改为666吗?答案是不会,因为A并不是指针引用,仍然会在遍历时进行值拷贝。

func testFor3() {
    type A struct {
        a string
    }
    m := map[string]A {
        "a": A{"123"},
        "b": A{"234"},
    }
    for _,v := range m {
        v.a = "666"
    }
    fmt.Println(m)
}

for 循环还可以被用于遍历管道:

func testFor4() {

    ch := make(chan int)
    go func() {
        // 需要关闭管道
        defer close(ch)
        for i := 0; i < 10 ; i ++{
            ch <- i
            time.Sleep(time.Millisecond * 500)
        }
    }()
    go func() {
        // for 遍历管道
        for {
            if data, ok := <-ch; true {
                if ok {
                    fmt.Println("for", data)
                } else {
                    break
                }
            }
        }

        /*
        下例是错误写法,因为data, ok := <-ch只在循环初始化第一次执行一次

            for data, ok := <-ch; true; {
                    if ok {
                        fmt.Println(data)
                    } else {
                        break
                    }
                }
            }
         */
    }()

    // 或者使用for range
    go func() {
        for v := range ch {
            fmt.Println("range", v)
        }
    }()
    time.Sleep(10 * time.Second)
}

上例的输出结果是随机的,因为有两个线程同时在消费管道。

for 还可以用于遍历字符串:

// byte
        for i := 0; i < len(s); i++ { 
            fmt.Printf("%v(%c) ", s[i], s[i])
        }
        fmt.Println()
//自动处理unicode为rune
        for _, r := range s { 
            fmt.Printf("%v(%c) ", r, r)
        }
        fmt.Println()

循环控制Goto、Break、Continue

break、continue用于跳出循环。goto则是原汁原味的c风格。三个语句都支持配合label标签使用。
下例如果去掉 end = true将会是个死循环。

func testGoto() {
    i := 0
    end := false
a:
    fmt.Println(i)
    if i < 10 {
        i++
        goto c
    }
    fmt.Println("i is 10")
    end = true
c:
    fmt.Println("run c")
    if !end {
        goto a
    }
}

select语句

  • 每个case都必须是一个通信
  • 所有channel表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
    否则:
  • 如果有default子句,则执行该语句。
  • 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
  • select 语句并不是if else的替代品,其中随机执行可执行的case通过if else 是无法实现的
func testSelect1() {
    var ch1 = make(chan int)
    var ch2 = make(chan string)

    go func() {
        ch1 <- 233
    }()

    go func() {
        ch2 <- "aaa"
    }()

    select {
    case a := <-ch1:
        fmt.Println("ch1", a)
    case b := <-ch2:
        fmt.Println("ch2", b)
    }
}

select 语句会从多个可执行的case随机取一个执行,未执行的case将不会实际读取通道的数据

func testSelect6() {
    ch1 := make(chan int, 2)
    ch2 := make(chan int, 2)

    fmt.Println(len(ch1))
    fmt.Println(len(ch2))

    go func() {
        ch1 <- 1
        ch2 <- 1
        ch1 <- 2
        ch2 <- 2
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(len(ch1))
    fmt.Println(len(ch2))

    // 有两个通道可以读取数据,但是只有随机执行到的case的通道才会真的读取
    // 因此执行完select,一个通道的len为2一个为1
    select {
    case <- ch1:
        fmt.Println("ch1")
    case <- ch2:
        fmt.Println("ch2")
    }
    fmt.Println(len(ch1))
    fmt.Println(len(ch2))
}

select 相比switch 的一个特性在于,它默认是会阻塞的(没有default的情况),配合for + select 可以实现一个消费者,消费来自多个生产者的内容,例如:

func testSelect2() {
    var ch1 = make(chan int)
    var ch2 = make(chan string)

    go func() {
        for i := 0; i <= 10; i++ {
            ch1 <- i
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        ch2 <- "aaa"
        time.Sleep(200 * time.Millisecond)
    }()

a:
    for {
        select {
        case a := <-ch1:
            fmt.Println("ch1", a)
            if a == 10 {
                fmt.Println("end")
                break a
            }
        case b := <-ch2:
            fmt.Println("ch2", b)
        }
    }
}

还可以用于超时控制:

func testSelect3(seconds int) {

    ch := make(chan int)

    go func() {
        time.Sleep(time.Duration(seconds) * time.Second)
        ch <- 123
    }()

    select {
    case v := <-ch:
        fmt.Println("success", v)
    // 当时间超出3s时自动退出
    case <- time.After(3 * time.Second):
        fmt.Println("timeout")
    }
}

优雅退出:

func testSelect4() {
    quitChan := make(chan struct{})
    // 启动一个协程,过5秒钟告诉主线程可以退出了
    go func() {
        time.Sleep(5 * time.Second)
        quitChan <- struct{}{}
    }()
    // 主线程
    for {
        fmt.Println("I am still alive.")
        time.Sleep(1 * time.Second)
        select {
        case <-quitChan:
            fmt.Println("clean something")
            fmt.Println("quit")
            return
        default:
            // 继续干活
        }
    }
}

判断通道是否已满

func testSelect5() {

    ch := make(chan int, 5)

    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
    }()

    time.Sleep(1 * time.Second)
    select {
    // 不仅可以出还可以进,如果满了就会阻塞
    case ch <- 0:
        fmt.Println("队列未满")
        // ...
    default:
        // 此时已经满了
        fmt.Println(len(ch))
        // 可以剔除一部分数据,此处剔除一半数据
        // 此外可以做某些操作,比如启动更多的线程or服务来消费数据
        for i, l := 0, len(ch); i < l/2; i++ {
            a := <-ch
            fmt.Println("del", a)
        }
    }
}

版权声明

知识共享许可协议 本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
发布时间:2021年03月09日 19:28:49

评论区#

还没有评论哦,期待您的评论!

关闭特效