匿名函数与闭包

匿名函数

简单来说:未指定函数名的函数就叫匿名函数

// 格式如下:
func(tname string){}("这里的括号代替了执行步骤")
// 等同于如下:
aFunc := func(tname string) string {}
aFunc("匿名-anonymous-函数")

// 完整示例(匿名函数也可以带返回值)
aRet := func(tname string){
    fmt.Println("hello...", tname)
    return tname
}("匿名-anonymous-函数")
fmt.Println("匿名函数返回值:", aRet)

匿名函数 可作为其他函数的参数使用
如下示例输出:4

aParamTest := func(a int, callback func(b int) int) {
    a++
    fmt.Println("匿名函数作为参数测试结果:")
    fmt.Println(callback(a))
}
aParamTest(1, func(x int) int {
    return x + 2
})
闭包

闭包是由函数及其相关引用环境组成的实体,可以理解为一个函数“捕获”了和它处于同一作用域的其他变量。
go 中所有匿名函数都是闭包
“捕获” 的本质就是 引用传递(引用类型) 而非 值传递 (注:go只有值传递,此处为方便理解写,引用传递,其本质是 指针的值传递)

func main() {
    i := 1
    defer func() {
        fmt.Println("defer-闭包i值:", i)
    }()
    defer fmt.Println("defer-i值:", i)
    i += 100
}

输出:
defer-i值: 1
defer-闭包i值: 101
函数被 deferred 时涉及的 参数变量值 在 defer 函数编写时确定,而非实际调用时(参考 资源释放-defer)
所以引用类型(闭包)的 i 可以输出 101,而非引用的类型,输出了 1(被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址)

关于资源释放-defer

类似于其他语言的 析构函数 比如PHP的 __destruct

func b() error {
    resp, err := http.Get("xxx.com")
    // 先判断操作是否成功
    if err != nil {
        return err
    }
    // 如果操作成功,再进行Close操作
    defer resp.Body.Close()
    return nil
}

上面使用defer简单展示了一下闭包的相关概念,下面使用一个更具体的返回类型为匿名函数的示例更深入的了解下闭包;
我们知道常规情况下,函数可以返回 int、array、map 等类型,但其实函数也是一种类型,也可用作返回值;
如下就是一个返回类型为函数的示例:

func closureTest(x int) func(int) int {
    return func(i int) int {
        x += i
        fmt.Printf("x addr %p, x value %d\n", &x, x)
        return x
    }
}
// 在main中调用
ctest := closureTest(1)
ctest(100)
ctest2 := closureTest(10)
ctest2(1)
// 输出:
// x value 101
// x value 11

上面的示例中同时返回了x的地址,你会发现,两次调用 closureTest 初始化时x的地址变了(同一个匿名函数可构造多个实例,每个实例内的 自由变量 地址不同);
但通过输出结果可以看到,x 参数在闭包中是引用类型传递,保留了 closureTest 初始化时的 1 和 10 的初值,所以输出 101、11;

那么,如何在闭包中实现值传递呢?答案是 通过匿名函数参数赋值的方式实现值传递;
我们来看另一个示例:

for i := 0; i < 3; i++ {
    go func() {
        fmt.Print(i, " ")
    }()
}
time.Sleep(time.Millisecond * 100) // 休眠以保证协程输出
// 输出:3 3 3

fmt.Println()
for i := 0; i < 3; i++ {
    go func(i int) {
        fmt.Print(i, " ")
    }(i)
}
time.Sleep(time.Millisecond * 100) // 休眠以保证协程输出
// 输出:1 0 2 (因为协程是乱序的,所以可能是 2 0 1 或者其他顺序,总之证明了匿名函数参数赋值实现了值传递)

Summary

  • 被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址
  • 匿名函数及其“捕获”的自由变量被称为闭包
  • 同一个匿名函数可构造多个实例,每个实例内的 自由变量 地址不同
  • 匿名函数内部的局部变量在每次执行匿名函数时地址都是变换的
  • 通过匿名函数参数赋值的方式可以实现值传递

后厂村村长
7 声望2 粉丝

Hello, Debug World