Go基礎系列:函數(2)——回調函數和閉包

回調函數和閉包

當函數具有如下兩種特性的時候,就能夠稱之爲高階函數(high order functions):html

  1. 函數能夠做爲另外一個函數的參數(典型用法是回調函數)
  2. 函數能夠返回另外一個函數,即讓另外一個函數做爲這個函數的返回值(典型用法是閉包)

通常來講,附帶的還具有一個特性:函數能夠做爲一個值賦值給變量。算法

f := func(){...}
f()

因爲Go中函數不能嵌套命名函數,因此函數返回函數的時候,只能返回匿名函數。閉包

先簡單介紹下高階函數,而後介紹閉包。less

高階函數示例

例如,將函數做爲另外一個函數的參數:函數

package main

import "fmt"

func added(msg string, a func(a, b int) int) {
    fmt.Println(msg, ":", a(33, 44))
}

func main() {
    // 函數內部不能嵌套命名函數
    // 因此main()中只能定義匿名函數
    f := func(a, b int) int {
        return a + b
    }
    added("a+b", f)
}

如下示例是函數返回另外一個函數:工具

package main

import "fmt"

func added() func(a, b int) int {
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {
    m := added()
    fmt.Println(m(33, 44))
}

回調函數(sort.SliceStable)

將函數B做爲另外一個函數A的參數,可使得函數A的通用性更強,能夠隨意定義函數B,只要知足規則,函數A均可以去處理,這比較適合於回調函數。code

在Go的sort包中有一個很強大的Slice排序工具SliceStable(),它就是將排序函數做爲參數的:orm

func SliceStable(slice interface{}, less func(i, j int) bool)

這個函數是什麼意思呢?給定一個名爲slice的Slice結構,使用名爲less的函數去對這個slice排序。這個less函數的結構爲less func(i, j int) bool,其中i和j指定排序依據。Go中已經內置好了排序的算法,咱們無需本身去定義排序算法,Go會自動從Slice中每次取兩個i和j索引對應的元素,而後去回調排序函數less。因此咱們只須要傳遞升序仍是降序、根據什麼排序就能夠。htm

例如:blog

package main

import (
    "fmt"
    "sort"
)

func main() {
    s1 := []int{112, 22, 52, 32, 12}
    // 定義排序函數
    less := func(i, j int) bool {
        // 降序排序
        return s1[i] > s1[j]
        // 升序排序:s1[i] < s1[j]
    }
    //
    sort.SliceStable(s1, less)
    fmt.Println(s1)
}

這裏的排序函數就是回調函數。每取一次i和j對應的元素,就調用一次less函數。

能夠將排序函數直接寫在SliceStable()的參數位置:

sort.SliceStable(s1, func(i, j int) bool {
    return s1[i] > s1[j]
})

還能夠更強大更靈活。例如,按照字符大小順序來比較,而不是按照數值大小比較:

package main

import (
    "fmt"
    "sort"
    "strconv"
)

func main() {
    s1 := []int{112, 220, 52, 32, 42}
    sort.SliceStable(s1, func(i, j int) bool {
        // 將i和j對應的元素值轉換成字符串
        bi := strconv.FormatInt(int64(s1[i]), 10)
        bj := strconv.FormatInt(int64(s1[j]), 10)
        // 按字符順序降序排序
        return bi > bj
    })
    fmt.Println(s1)
}

按照字符串長度來比較:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s1 := []string{"hello","malong","gaoxiao"}
    sort.SliceStable(s1, func(i, j int) bool {
        // 按字節大小順序降序排序
        return len(s1[i]) > len(s1[j])
    })
    fmt.Println(s1)
}

更嚴格地說是按字節數比較,由於len()操做字符串時獲取的是字節數而非字符數。若是要按照字符數比較,則使用以下代碼:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s1 := []string{"hello","世界","gaoxiao"}
    sort.SliceStable(s1, func(i, j int) bool {
        // 按字節大小順序降序排序
        return len([]rune(s1[i])) > len([]rune(s1[j]))
    })
    fmt.Println(s1)
}

閉包

函數A返回函數B,最典型的用法就是閉包(closure)。

關於閉包詳細的定義,見個人另外一篇文章,無比詳細一文搞懂:詞法做用域、動態做用域、回調函數、閉包

簡單地說,閉包就是"一個函數+一個做用域環境"組成的特殊函數。這個函數能夠訪問不是它本身內部的變量,也就是這個變量在其它做用域內,且這個變量是未賦值的,而是等待咱們去賦值的。

例如:

package main

import "fmt"

func f(x int) func(int) int{
    g := func(y int) int{
        return x+y
    }
    // 返回閉包
    return g
}

func main() {
    // 將函數的返回結果"閉包"賦值給變量a
    a := f(3)
    // 調用存儲在變量中的閉包函數
    res := a(5)
    fmt.Println(res)

    // 能夠直接調用閉包
    // 由於閉包沒有賦值給變量,因此它稱爲匿名閉包
    fmt.Println(f(3)(5))
}

上面的f()返回的g之因此稱爲閉包函數,是由於它是一個函數,且引用了不在它本身範圍內的變量x,這個變量x是g所在做用域環境內的變量,由於x是未知、未賦值的自由變量。

若是x在傳遞給g以前是已經賦值的,那麼閉包函數就不該該稱爲閉包,由於這樣的閉包已經失去意義了。

下面這個g也是閉包函數,但這個閉包函數是自定義的,而不是經過函數返回函數獲得的。

package main

import "fmt"

func main() {
    // 自由變量x
    var x int
    // 閉包函數g
    g := func(i int) int {
        return x + i
    }
    x = 5
    // 調用閉包函數
    fmt.Println(g(5))
    x = 10
    // 調用閉包函數
    fmt.Println(g(3))
}

之因此這裏的g也是閉包函數,是由於g中訪問了不屬於本身的變量x,而這個變量在閉包函數定義時是未綁定值的,也就是自由變量。

閉包的做用很明顯,在第一個閉包例子中,f(3)退出後,它返回的閉包函數g()仍然記住了本來屬於f()中的x=3。這樣就可讓不少閉包函數共享同一個自由變量x的值

例如,下面的a(3)a(5)a(8)都共享來自f()的x=3

a := f(3)
a(3)
a(5)
a(8)

再往外層函數看,f(3)能夠將自由變量x綁定爲x=3,天然也能夠綁定爲x=5x=8等等。

因此,何時使用閉包?通常來講,能夠將過程分紅兩部分或更多部分都進行工廠化的時候,就適合閉包(實際上,有些地方直接將閉包稱呼爲工廠函數)。第一個部分是能夠給自由變量批量綁定不一樣的值,第二部分是多個閉包函數能夠共享第一步綁定後的自由變量

相關文章
相關標籤/搜索