golang 函数二 (匿名函数和闭包)

2023-06-12,,

匿名函数就是没有定义函数名称的函数。我们可以在函数内部定义匿名函数,也叫函数嵌套。

匿名函数可以直接被调用,也可以赋值给变量、作为参数或返回值。比如:

func main(){
    func(s string){     //直接被调用
        println(s)
    }("hello gopher!!!")
    /*
    func(s string){     //未被调用
        println(s)
    }
    */
}

func main(){
    hi := func(s string){   //赋值给变量
        println(s)
    }
    hi("hello gopher!!!")
}


func test(f func(string)){
    f("hello gopher!!!")
}
func main(){
    hi := func(s string){
        println(s)
    }
    test(hi)     //作为参数
}

func test()func(string){
    hi := func(s string){   //作为返回值
        println(s)
    }
    return hi
}

func main(){
    f := test()
    f("hello gopher!!!")
}

普通函数和匿名函数都可以作为结构体的字段,比如:

{
    type calc struct{
        mul func(x,y int)int
    }
    x := calc{
        mul: func(x,y int)int{
            return (x*y)
        },
    }
    println(x.mul(2,3))
}

也可以经channel(通道)传递,比如:

{
    c := make(chan func(int, int)int, 2)
    c <- func(x,y int) int {return x + y}
    println((<-c)(2,3))
}

闭包(closure)

闭包是指在上下文中引用了自由变量(未绑定到特定对象)的代码块(函数),或者说是代码块(函数)与和引用环境的组合体。比如:

func intSeq()func()int{
    i := 0
    println(&i)
    return func()int{
        i += 1
        println(&i,i)
        return i
    }   
}
                                                                                                                                                      
func main(){
    nextInt := intSeq()
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    newInt := intSeq()
    fmt.Println(newInt())
}
输出:
0xc42000a320
0xc42000a320 1
1
0xc42000a320 2
2
0xc42000a320 3
3
0xc42007a010
0xc42007a010 1
1

当nextInt函数返回后,通过输出指针,我们可以看出函数在main运行时,依然引用的是原环境变量指针,这种现象称作闭包。所以说,闭包是函数和引用环境变量的组合体。

因为闭包是通过指针引用环境变量,那么就会导致该变量的生命周期

变长,甚至被分配到堆内存。如果多个匿名函数引用同一个环境变量,会让事情变得更加复杂,比如:

func test()[]func(){
    var s []func()
    for i:= 0;i < 3;i++{
        s =  append(s, func(){
            println(&i , i)
        })
    }
    return s
}
func main(){
    funcSlice := test()
    for _ , f := range funcSlice{
        f()
    }
}
输出:
0xc42000a320 3
0xc42000a320 3
0xc42000a320 3

解决方法就是每次用不同的环境变量或参数赋值,比如修改后的test函数:

func test()[]func(){
    var s []func()
    for i:= 0;i < 3;i++{
        x := i
        s =  append(s, func(){
            println(&x , x)
        })
    }
    return s
}

闭包在不用传递参数的情况下就可以读取和修改环境变量,当然我们是要为这种遍历付出代价的,所以日常开发中,在高并发服务

的场景下建议慎用,除非你明确你的需求必须这样做。

《golang 函数二 (匿名函数和闭包).doc》

下载本文的Word格式文档,以方便收藏与打印。