Go 断言与闭包

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Go 断言与闭包

树獭叔叔   2022-09-28 我要评论

1. Go断言的使用

Go中的断言用于判断变量的类型,其使用形式如下所示:

value, ok := x.(T)

上面的代码是判断x是否为T类型的变量:

  • 如果 T 的某个具体的类型,断言会检查 x 是否为该类型,如果是的话买就返回 x 以及一个布尔值true,反之返回一个false
  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,返回值是一个类型为 T 的接口值,以及一个布尔值true,反之返回一个false
  • 我们也可以不接受返回的布尔值,在这种情况下,如果断言失败,会直接panic,所以非常不推荐这种处理方式

另外,断言和可以与switch配合使用

    switch a.(type) {
        case int:
        fmt.Println("the type of a is int")
        case string:
        fmt.Println("the type of a is string")
        case float64:
        fmt.Println("the type of a is float")
        default:
        fmt.Println("unknown type")
    }

2. 闭包的解读

闭包是由函数和与其相关的引用环境组合而成的实体。

概念上说起来有些抽象,下面我们以一个具体的例子来理解。

func foo1(x int) func() {
  return func() {
    x = x + 1
    fmt.Printf("foo2 val = %d\n", x)
  }
}

f1 := foo1(1)
f1() // 2
f1() // 3

在上面的例子中,f1() 与他的变量x(值为1)共同组成了一个闭包,每次调用f1(),x的值就会+1并且打印。

从某种意义上来说,闭包延长了变量的生命周期(栈上分配改为了堆上分配)。

2.1 指针传递

func foo2(x *int) func() {
  return func() {
    *x = *x + 1
    fmt.Printf("foo2 val = %d\n", *x)
  }
}

x := 1
f1 := foo2(&x)
f2 := foo2(&x)
f1() // 2
f2() // 3

通过第一个例子,我们知道,函数以及其环境(传入的变量)组成了闭包,这个时候,如果传入的是一个指针,那么就会存在多个闭包共用一个变量的情况。

2.2 延迟绑定

闭包的延迟绑定,通俗地说,就是闭包的函数在第一次调用的时候才会与环境的变量进行绑定,我们依然以上面提到的两个函数为例子:

func foo1(x int) func() {
  return func() {
    x = x + 1
    fmt.Printf("foo2 val = %d\n", x)
  }
}

func foo2(x *int) func() {
  return func() {
    *x = *x + 1
    fmt.Printf("foo2 val = %d\n", *x)
  }
}

x := 1
f1 := foo1(x)
f2 := foo2(&x)
f2() // 2
f1() // 3
  • 我们创建了f1与f2两个闭包函数,以及变量 x 的值为1
  • 在f1与f2创建的时候,变量并没有与函数绑定
  • 第一次调用f2()时,&x与其绑定,x的值+1,变为2
  • 第一次调用f1()时,x与其绑定,这时x已经变为2了,再+1,所以变为3

2.3 Go Routine的延迟绑定

我们在一个函数中启动 Go Routine 调用另一个函数:

func show(v interface{}) {
    fmt.Printf("foo4 val = %v\n", v)
}
func foo4() {
    values := []int{1, 2, 3, 5}
    for _, val := range values {
        go show(val)
    }
}
​
foo4()
//foo3 val = 2
//foo3 val = 3
//foo3 val = 1
//foo3 val = 5

因为Go Routine的执行顺序是随机并行的,因此执行多次foo4()输出的顺序不一行相同,但是一定打印了“1,2,3,5”各个元素。

但是,如果我们以匿名函数的形式尝试复现上面的逻辑,会发现:

func foo5() {
    values := []int{1, 2, 3, 5}
    for _, val := range values {
        go func() {
            fmt.Printf("foo5 val = %v\n", val)
        }()
    }
}

foo5()
//foo3 val = 5
//foo3 val = 5
//foo3 val = 5
//foo3 val = 5

其实这个问题的本质同闭包的延迟绑定,或者说,这段匿名函数的对象就是闭包。在我们调用go func() { xxx }()的时候,只要没有真正开始执行这段代码,那它还只是一段函数声明。而在这段匿名函数被执行的时候,才是内部变量寻找真正赋值的时候。for-loop的遍历几乎是“瞬时”完成的,4个Go Routine真正被执行在其后,所以会产生上面的情况。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们