匿名函数与闭包
匿名函数
简单来说:未指定函数名的函数就叫匿名函数
// 格式如下:
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
- 被闭包捕获的变量称为“自由变量”,在匿名函数实例未消亡时共享同个内存地址
- 匿名函数及其“捕获”的自由变量被称为闭包
- 同一个匿名函数可构造多个实例,每个实例内的 自由变量 地址不同
- 匿名函数内部的局部变量在每次执行匿名函数时地址都是变换的
通过匿名函数参数赋值的方式可以实现值传递
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。