感觉今天应该是对匿名函数的使用开窍了😃
概念
《Go语言圣经》
使用匿名函数可以访问完整的词法环境,也就是在函数中定义的内部函数可以引用该函数的变量。
// squares返回一个匿名函数。
// 该匿名函数每次被调用时都会返回下一个数的平方。
func squares() func() int {
var x int
return func() int { // 使用了上一行的变量x ,虽然x不是在这个匿名函数里定义的
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
函数squares返回另一个类型为func() int
的函数。对squares
的一次调用会生成一个局部变量x
并返回一个匿名函数。每次调用匿名函数时,该函数都会先使x
的值加1,再返回x
的平方。第二次调用squares
时,会生成第二个x
变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x
变量。
squares
的例子证明,函数值不仅仅是一串代码,还记录了状态。在squares
中定义的匿名内部函数可以访问和更新squares
中的局部变量,这意味着匿名函数和squares
中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。
通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares
返回后,变量x仍然隐式的存在于f
中。这就是多次调用f()
后,可以看到x
的值是随上一次增长的,我们可以把x
看作存在与f
的变量,它们有相同的生命周期。
外部函数对匿名函数的参数是指针传递的
Golang一般来说,全部是值传递;匿名函数除外,匿名函数接收一个指针。
// 通常来讲,go是值传递的
// 但是闭包除外,它是引用传递
func errorUsing() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
func correctUsing() {
for i := 0; i < 3; i+t+ {
defer func(it int) { // 每次迭代用新的局部变量记录i的值
fmt.Println(it)
}(i)
}
}
func main() {
errorUsing() // 3 3 3
correctUsing() // 2 1 0
}
函数errorUsing()
中,执行了三次defer func()
,使用指针传递i
,所以是指向同一个地址的,对defer func()
压栈后,出栈输出时,这个地址指向的i
的值为3。因为栈底到栈顶的这三个defer func()
的i
是同一个地址,所以输出的值相同。
函数correctUsing()
每次调用defer func()
,将传入的指向i
的指针赋值了一份,传给形参it
,所以拿到的it
和i
是两个不同的地址空间,同理,三次压栈的defer func()
每个的参数it
都是不同的地址空间,所以能把三个不同的i
打印出来。
使用匿名函数
**让函数带着参数走,不用显示的传参。**因为有些情况下,项目开发中很多函数已经定型了,如果某个地方需要新的参数,但是又不好修改函数声明,这时候就可以考虑使用匿名函数传值。
这是我的一个理解,就像概念中的Demo,函数f()
带着参数x
走,而且不用显示的接收参数。
Demo1-文件的Read和Close
func doFile() {
// 假设 我们定义一个方法,但是不用显示的使用 f
// 就需要在 定义函数时 使用闭包 把 f 传进去
f, _ := os.OpenFile("filename.txt", os.O_RDWR|os.O_CREATE, 0755)
// 通常的,匿名函数用来直接使用函数外的变量,利用的就是这一个特性
var (
Close = func() error {
return (*os.File).Close(f) // 传入外部的 f
}
Read = func(data []byte) (int, error) {
return (*os.File).Read(f, data) // 传入外部的 f
}
)
// 不再依赖 f,只要是个 *File 类型的都可以放
Read([]byte("data"))
Close()
}
可以看到,这里Read
的声明是一个这样的函数:接收一个[]byte
,返回一个(int, err)
。
而(*os.File).Read(f, data)
中的Read
是这样的:
它是类型*File
的一个方法,接收[]byte
,返回int, err
。使用时需要Read(f, data)
两个参数。
使用闭包将*File
传入后,新的包装后的Read([]byte)
只需要一个参数就行了,f
已经隐式的包含在里面了。
Demo2-依赖注入(串联http处理函数handlerFunc)
假设有这样一段:
func main() {
server := http.Server{
Addr : "127.0.0.1:8080",
}
http.HandleFunc("/post", handleRequest)
server.ListenAndServe()
}
// 一个多路复用器
func handleRequest(w http.ResponseWriter, r *http.Request) {
var err error
switch r.Method {
case "GET":
err = handleGet(w, r)
case "POST":
...
...
}
}
// 对应的处理函数
func handleGet(w http.ResponseWriter, r *http.Request) (err error) {
...
}
如果,现在有这么一种需求,函数handleGet(w,r)
多了一个参数需求,它想变成handleGet(w, r, t)
,同时这个参数要在main()
中传给handleRequest
以便给后面所有从它复用出来的处理器使用。
所以,有下面这种方法:
// 一个 使用匿名函数返回 一个 多路复用器的函数 handleRequest
func handleRequest(t Text) http.HandlerFunc {
// 通过匿名函数 把 t 传进去
return func(w http.ResponseWriter, r *http.Request) {
var err error
switch r.Method {
case "GET":
err = handleGet(w, r, t) // 可以使用t
case "POST":
...
...
}
}
}
func main() {
t := &Text{...} // 创建T的实例
server := http.Server{
Addr : "127.0.0.1:8080",
}
http.HandleFunc("/post", handleRequest(t)) // line 1
// line 1 也就等同于下面这两行
// h := handleRequest(t)
// http.HandleFunc("/post", h) // 为什么不是 h() 这个和 直接使用一个 handleFunc 是一个道理,不用加 ()
server.ListenAndServe()
}
这里就使用了匿名函数,事先把它传给handleRequest(t Text)
,从而没有影响http.HandleFunc()
中需要调用的实现了ServerHTTP()
的处理器。