golang 函數二 (匿名函數和閉包)

匿名函數就是沒有定義函數名稱的函數。我們可以在函數內部定義匿名函數,也叫函數嵌套。

匿名函數可以直接被調用,也可以賦值給變量、作爲參數或返回值。比如:

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
}

閉包在不用傳遞參數的情況下就可以讀取和修改環境變量,當然我們是要爲這種遍歷付出代價的,所以日常開發中,在高併發服務

的場景下建議慎用,除非你明確你的需求必須這樣做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章