go的defer和闭包(例子说明,非内部实现)

用几个例子说明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 代码的关键。
当在闭包中操作变量时,要注意变量作用域、捕获的变量副本和对外部作用域的影响。

你可能感兴趣的:(闭包go)