- 闭包相当于保存了外部环境的变量的引用
- 如果不会修改外部变量,会退化为值
- 闭包中对外部变量的改变也会影响外部变量的值
- 同样外部变量的改变也会影响闭包内的值
// 闭包底层实现原理
type Closure struct {
F unitptr // 函数指针
env *Type //使用到的外部环境变量指针;如果没有修改,则会优化为值传递
- 编译器检测报告闭包时,会把闭包引用的外部变量分配到堆上
- 闭包的目的之一是减少全局变量
- 在函数调用时隐式传递共享变量
- 但这种共享变量方式不够清晰
闭包的坑
-
在for循环中使用闭包时,应该把迭代变量作为参数传入闭包
- 例如
for k, v := range m
中的k, v,for i :=0; i < 10; i++
中的i - 原因在于k,v,i在迭代的过程中地址不变,而是不断被重新赋值
- 因此每次迭代闭包引用的都是同一个地址,所以都得到的是同样的值
- 例如
-
如果函数返回了一个闭包,使用该函数构造新函数并调用多次,则每次调用该新函数造成的影响是累积的
- 因为闭包函数共享外部引用
- 如果闭包引用了全局变量,则每次调用新函数都会影响全局变量
- 最好不要把闭包和全局变量混合使用
func fa(a int) func(i int) int {
return func(i int) int {
fmt.Println(&a, a)
a = a + i
return a
}
}
f := fa(1)
fmt.Println(f(1))
fmt.Println(f(1))
// 输出
0x1234 1
2
0x1234 2
3
- 如果构造了多个新函数,则这些新函数之间的调用互相不影响
f := fa(1)
g := fa(1)
fmt.Println(f(1))
fmt.Println(g(1))
// 输出
0x1234 1
2
0x5678 1
2
- 闭包+defer可能在return后改变返回值
func f() (res int) {
defer func() {
res++
}
return 0
}
fmt.Println(f()) // 输出1,而不是0
闭包的使用
- 闭包可以用来构造工厂函数
- 返回值为另一个函数的函数可以被称之为工厂函数
- 这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数
func MakeAddSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
addBmp := MakeAddSuffix(".bmp")
addJpeg := MakeAddSuffix(".jpeg")
addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg
- 闭包可以用来返回关闭函数
- 关闭打开的文件,通道等
- 调用者在关闭时不用知道内部细节
// 返回的removeFile函数可以关闭函数内打开的临时文件,即使函数返回后文件名已不可知
func createTempFile(initialData string) (io.ReadWriteSeeker, func()) {
tmpfile, err := ioutil.TempFile("", "db")
if err != nil {
fmt.Printf("could not create temp file %v", err)
}
tmpfile.Write([]byte(initialData))
removeFile := func() {
os.Remove(tmpfile.Name())
}
return tmpfile, removeFile
}
// 调用者
database, cleanDatabase := createTempFile(`[
{"Name": "Cleo", "Wins": 10},
{"Name": "Chris", "Wins": 33}]`)
defer cleanDatabase()