用几个例子阐明 golang 的闭包函数,联合 defer 应用,配合对应代码及文末总结。
函数 | 阐明 | 输入 |
---|---|---|
e1 | defer 调用,相当于是拿到了以后 err 变量的快照,即注册 defer 函数的时候,将当下 err 的值塞入到 defer 中 | start err1 |
e2 | defer 调用,然而一个闭包函数,且闭包函数有传参,闭包捕捉以后 err 的值依然是 start err2(闭包捕捉的是变量值的拷贝),且闭包内的值变量扭转不会影响内部 err 的值(详见见 e5) | start err2 |
e3 | defer 调用,闭包内的变量和匿名函数外的变量是专用的,没有传递形参,没有传递形参,与上下文共享 | defer3 error |
e4 | defer 调用,在函数 e4 中,当你将 err 作为参数传递给闭包函数时,实际上是创立了一个闭包函数的正本,这个正本在闭包外部独立于内部作用域。这种行为是因为闭包在捕捉内部变量时,会将内部变量的以后值复制到闭包外部,造成一个闭包环境,当初了解了闭包的概念了吧。具体来说,在 defer 语句执行的时候,闭包函数会将 err 的以后值(即 “start err4″)复制到闭包外部的参数中。之后,无论内部作用域的 err 是否产生扭转,闭包外部的参数值都会放弃不变,因为闭包曾经捕捉了一个快照 | start err4 |
e5 | 传值的状况下,闭包内的值变量扭转不会影响内部 err 的值,(相互独立) | now err is start err5 start err5CHANGE ME |
e6 | 闭包没有传值,拿到的 err 是最初赋值的, | now err is start err6 defer6 error CHANGE ME |
package main
import (
"errors"
"fmt"
)
func e1(){err := errors.New("start err1")
defer fmt.Println(err)
err = errors.New("defer1 error")
return
}
func e2(){err := errors.New("start err2")
defer func(e error) {fmt.Println(e)
}(err)
err = errors.New("defer2 error")
return
}
func e3(){err := errors.New("start err3")
// 闭包内的变量和匿名函数外的变量是专用的,没有传递形参,没有传递形参,与上下文共享
defer func() {fmt.Println(err)
}()
err = errors.New("defer3 error")
return
}
func e4(){
var err error
err = errors.New("start err4")
// 闭包内的变量和匿名函数外的变量是专用的,然而如果传了形参,那就和上文的共用了
// 在函数 e4 中,当你将 err 作为参数传递给闭包函数时,实际上是创立了一个闭包函数的正本,这个正本在闭包外部独立于内部作用域。这种行为是因为闭包在捕捉内部变量时,会将内部变量的以后值复制到闭包外部,造成一个闭包环境
// 具体来说,在 defer 语句执行的时候,闭包函数会将 err 的以后值(即 "start err4")复制到闭包外部的参数中。之后,无论内部作用域的 err 是否产生扭转,闭包外部的参数值都会放弃不变,因为闭包曾经捕捉了一个快照。defer func(err error) {fmt.Println(err)
}(err)
err = errors.New("defer4 error")
return
}
func e5(){err := errors.New("start err4")
defer func(err error) {err=errors.New(err.Error()+"CHANGE ME")
fmt.Println(err)
}(err)
fmt.Println("now err is",err)
err = errors.New("defer5 error")
return
}
func e6() {err := errors.New("start err6")
defer func() {err = errors.New(err.Error() + "CHANGE ME")
fmt.Println(err)
}()
fmt.Println("now err is", err)
err = errors.New("defer6 error")
return
}
func main(){e1()
e2()
e3()
e4()
e5()
e6()}
变量作用域和闭包:
Go 语言中的变量作用域由代码块决定。变量在其定义的代码块内可见。
闭包是一个函数值,它能够捕捉其定义时四周的作用域内的变量。
闭包能够在定义之外被调用,依然拜访并批改捕捉的变量。
闭包和变量捕捉:
闭包函数能够捕捉内部作用域的变量。在闭包外部,它们能够拜访内部变量的值。
闭包捕捉的变量是其正本,即闭包外部应用的是变量值的拷贝。
批改闭包外部捕捉的变量不会影响内部作用域中的变量,除非你在闭包内间接批改内部作用域的变量。
闭包参数传递:
在闭包外部接管内部作用域的变量作为参数,能够使闭包操作内部作用域的变量。
应用闭包参数传递能够无效隔离闭包内外的变量,从而放弃可预测性。
在 defer 中的闭包:
当在 defer 语句中应用闭包时,闭包外部的变量会被“捕捉”并在 defer 执行时应用。
在闭包外部批改闭包捕捉的变量不会影响内部作用域中的变量,除非你间接批改内部作用域的变量。
总结:
闭包是一种弱小的概念,能够使函数领有状态并提早执行。
理解闭包如何操作变量作用域,以及它们如何捕捉和批改变量,是编写高效、清晰的 Go 代码的要害。
当在闭包中操作变量时,要留神变量作用域、捕捉的变量正本和对外部作用域的影响。