golang 閉包

提及golang閉包,在官方手冊裏面看過一次,沒怎麼用過,仍是由於6哥常常用,閱讀他的代碼好多閉包,emmm,今天就學習一下。python

在過去近十年時間裏,面向對象編程大行其道,以致於在大學的教育裏,老師也只會教給咱們兩種編程模型,面向過程和麪向對象。孰不知,在面向對象思想產生以前,函數式編程已經有了數十年的歷史。就讓咱們回顧這個古老又現代的編程模型,看看到底是什麼魔力將這個概念在21世紀的今天再次拉入咱們的視野golang

閉包是函數式編程語言中的概念,沒有研究過函數式語言的人可能很難理解閉包的強大(我就是其中一個,看見的第一眼就是一臉懵逼)編程

閉包=函數+引用環境設計模式

所謂閉包是指內層函數引用了外層函數中的變量或稱爲引用了自由變量的函數,其返回值也是一個函數,瞭解過的語言中有閉包概念的像 js,python,golang 都相似這樣。數組

閉包只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就肯定了,不會在執行時發生變化,因此一個函數只有一個實例。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。所謂引用環境是指在程序執行中的某個點全部處於活躍狀態的約束所組成的集合。其中的約束是指一個變量的名字和其所表明的對象之間的聯繫。那麼爲何要把引用環境與函數組合起來呢?這主要是由於在支持嵌套做用域的語言中,有時不能簡單直接地肯定函數的引用環境。這樣的語言通常具備這樣的特性網絡

函數是一等公民(First-class value),即函數能夠做爲另外一個函數的返回值或參數,還能夠做爲一個變量的值。
函數能夠嵌套定義,即在一個函數內部能夠定義另外一個函數。閉包

在面向對象編程中,咱們把對象傳來傳去,那在函數式編程中,要作的是把函數傳來傳去,說成術語,把他叫作高階函數。在數學和計算機科學中,高階函數是至少知足下列一個條件的函數:app

接受一個或多個函數做爲輸入
輸出一個函數編程語言

 

 

上代碼,最基礎的閉包方式函數式編程

package main
 
import (
    "fmt"
)
 
func outer(x int) func(int) int {
    return func(y int) int {
        return x + y 
    }   
}
 
func main() {
    f := outer(10)
    fmt.Println(f(100))
} 

單純看return x+y

就知道返回結果是什麼。也就是110.

上面的例子還不夠簡單,再來一個

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}

函數f返回了一個函數,返回的這個函數就是一個閉包。這個函數自己中沒有定義變量I的,而是引用了它所在的環境(函數f)中的變量i。

package main

import "fmt"

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}

func main() {
    a := f(0)
    fmt.Println(a())
    fmt.Println(a())
    fmt.Println(a())
    fmt.Println(a())
    fmt.Println(a())

}

// PS C:\Users\13584\go\src\awesomeProject> go run .\main.go
// 1
// 2
// 3
// 4
// 5

函數f每進入一次,就造成了一個新的環境,對應的閉包中,函數都是同一個函數,環境倒是引用不一樣的環境。

變量i是函數f中的局部變量,假設這個變量是在函數f的棧中分配的,是不能夠的。由於函數f返回之後,對應的棧就失效了,f返回的那個函數中變量i就引用一個失效的位置了。因此閉包的環境中引用的變量不可以在棧上分配。

閉包結構體

回到閉包的實現來,前面說過,閉包是函數和它所引用的環境。那麼是否是能夠表示爲一個結構體呢:

type Closure struct {
    F func()() 
    i *int
}

事實上,Go在底層確實就是這樣表示一個閉包的。讓咱們看一下彙編代碼:

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}


MOVQ    $type.int+0(SB),(SP)
PCDATA    $0,$16
PCDATA    $1,$0
CALL    ,runtime.new(SB)    // 是否是很熟悉,這一段就是i = new(int)    
...    
MOVQ    $type.struct { F uintptr; A0 *int }+0(SB),(SP)    // 這個結構體就是閉包的類型
...
CALL    ,runtime.new(SB)    // 接下來至關於 new(Closure)
PCDATA    $0,$-1
MOVQ    8(SP),AX
NOP    ,
MOVQ    $"".func·001+0(SB),BP
MOVQ    BP,(AX)                // 函數地址賦值給Closure的F部分
NOP    ,
MOVQ    "".&i+16(SP),BP        // 將堆中new的變量i的地址賦值給Closure的值部分
MOVQ    BP,8(AX)
MOVQ    AX,"".~r1+40(FP)
ADDQ    $24,SP
RET    ,

其中func·001是另外一個函數的函數地址,也就是f返回的那個函數。

 

閉包小結:
函數只是一段可執行代碼,編譯後就「固化」了,每一個函數在內存中只有一份實例,獲得函數的入口點即可以執行函數了。在函數式編程語言中,函數是一等公民(First class value):第一類對象,咱們不須要像命令式語言中那樣藉助函數指針,委託操做函數,函數能夠做爲另外一個函數的參數或返回值,能夠賦給一個變量。函數能夠嵌套定義,即在一個函數內部能夠定義另外一個函數,有了嵌套函數這種結構,便會產生閉包問題。如:

package main
 
import (
    "fmt"
)
 
func adder() func(int) int {
    sum := 0
    innerfunc := func(x int) int {
        sum += x
        return sum
    }
    return innerfunc
}
 
func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(pos(i), neg(-2*i))
    }
 
}

在這段程序中,函數innerfunc是函數adder的內嵌函數,而且是adder函數的返回值。咱們注意到一個問題:內嵌函數innerfunc中引用到外層函數中的局部變量sum,Go會這麼處理這個問題呢?先讓咱們來看看這段代碼的運行結果:

0 0  
1 -2  
3 -6  
6 -12   
10 -20  
15 -30  
21 -42  
28 -56  
36 -72  
45 -90

注意:Go不能在函數內部顯式嵌套定義函數,可是能夠定義一個匿名函數。如上面所示,咱們定義了一個匿名函數對象,而後將其賦值給innerfunc,最後將其做爲返回值返回。

當用不一樣的參數調用adder函數獲得(pos(i),neg(i))函數時,獲得的結果是隔離的,也就是說每次調用adder返回的函數都將生成並保存一個新的局部變量sum。其實這裏adder函數返回的就是閉包。
這個就是Go中的閉包,一個函數和與其相關的引用環境組合而成的實體。一句關於閉包的名言: 對象是附有行爲的數據,而閉包是附有數據的行爲。

三 閉包使用

閉包常常用於回調函數,當IO操做(例如從網絡獲取數據、文件讀寫)完成的時候,會對獲取的數據進行某些操做,這些操做能夠交給函數對象處理。

除此以外,在一些公共的操做中常常會包含一些差別性的特殊操做,而這些差別性的操做能夠用函數來進行封裝。看下面的例子:

package main
 
import (
    "errors"
    "fmt"
)
 
type Traveser func(ele interface{})
/*
    Process:封裝公共切片數組操做
*/
func Process(array interface{}, traveser Traveser) error {
 
    if array == nil {
        return errors.New("nil pointer")
    }
    var length int //數組的長度
    switch array.(type) {
    case []int:
        length = len(array.([]int))
    case []string:
        length = len(array.([]string))
    case []float32:
        length = len(array.([]float32))
        default:
        return errors.New("error type")
    }
    if length == 0 {
        return errors.New("len is zero.")
    }
    traveser(array)
    return nil
}
/*
    具體操做:升序排序數組元素
*/
func SortByAscending(ele interface{}) {
    intSlice, ok := ele.([]int)
    if !ok {
        return
    }
    length := len(intSlice)
 
    for i := 0; i < length-1; i++ {
        isChange := false
        for j := 0; j < length-1-i; j++ {
 
            if intSlice[j] > intSlice[j+1] {
                isChange = true
                intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j]
            }
        }
 
        if isChange == false {
            return
        }
 
    }
}
/*
    具體操做:降序排序數組元素
*/
func SortByDescending(ele interface{}) {
 
    intSlice, ok := ele.([]int)
    if !ok {
        return
    }
    length := len(intSlice)
    for i := 0; i < length-1; i++ {
        isChange := false
        for j := 0; j < length-1-i; j++ {
            if intSlice[j] < intSlice[j+1] {
                isChange = true
                intSlice[j], intSlice[j+1] = intSlice[j+1], intSlice[j]
            }
        }
 
        if isChange == false {
            return
        }
 
    }
}
 
func main() {
 
    intSlice := make([]int, 0)
    intSlice = append(intSlice, 3, 1, 4, 2)
 
    Process(intSlice, SortByDescending)
    fmt.Println(intSlice) //[4 3 2 1]
    Process(intSlice, SortByAscending)
    fmt.Println(intSlice) //[1 2 3 4]
 
    stringSlice := make([]string, 0)
    stringSlice = append(stringSlice, "hello", "world", "china")
 
    /*
       具體操做:使用匿名函數封裝輸出操做
    */
    Process(stringSlice, func(elem interface{}) {
 
        if slice, ok := elem.([]string); ok {
            for index, value := range slice {
                fmt.Println("index:", index, "  value:", value)
            }
        }
    })
    floatSlice := make([]float32, 0)
    floatSlice = append(floatSlice, 1.2, 3.4, 2.4)
 
    /*
       具體操做:使用匿名函數封裝自定義操做
    */
    Process(floatSlice, func(elem interface{}) {
 
        if slice, ok := elem.([]float32); ok {
            for index, value := range slice {
                slice[index] = value * 2
            }
        }
    })
    fmt.Println(floatSlice) //[2.4 6.8 4.8]
}

輸出結果

[4 3 2 1]   
[1 2 3 4]
index: 0   value: hello
index: 1   value: world
index: 2   value: china
[2.4 6.8 4.8]

在上面的例子中,Process函數負責對切片(數組)數據進行操做,在操做切片(數組)時候,首先要作一些參數檢測,例如指針是否爲空、數組長度是否大於0等。這些是操做數據的公共操做。具體針對數據能夠有本身特殊的操做,包括排序(升序、降序)、輸出等。針對這些特殊的操做可使用函數對象來進行封裝。
再看下面的例子,這個例子沒什麼實際意義,只是爲了說明閉包的使用方式。

package main
 
import (
    "fmt"
)
 
type FilterFunc func(ele interface{}) interface{}
 
/*
  公共操做:對數據進行特殊操做
*/
func Data(arr interface{}, filterFunc FilterFunc) interface{} {
 
    slice := make([]int, 0)
    array, _ := arr.([]int)
 
    for _, value := range array {
 
        integer, ok := filterFunc(value).(int)
        if ok {
            slice = append(slice, integer)
        }
 
    }
    return slice
}
/*
  具體操做:奇數變偶數(這裏能夠不使用接口類型,直接使用int類型)
*/
func EvenFilter(ele interface{}) interface{} {
 
    integer, ok := ele.(int)
    if ok {
        if integer%2 == 1 {
            integer = integer + 1
        }
    }
    return integer
}
/*
  具體操做:偶數變奇數(這裏能夠不使用接口類型,直接使用int類型)
*/
func OddFilter(ele interface{}) interface{} {
 
    integer, ok := ele.(int)
 
    if ok {
        if integer%2 != 1 {
            integer = integer + 1
        }
    }
 
    return integer
}
 
func main() {
    sliceEven := make([]int, 0)
    sliceEven = append(sliceEven, 1, 2, 3, 4, 5)
    sliceEven = Data(sliceEven, EvenFilter).([]int)
    fmt.Println(sliceEven) //[2 2 4 4 6]
 
    sliceOdd := make([]int, 0)
    sliceOdd = append(sliceOdd, 1, 2, 3, 4, 5)
    sliceOdd = Data(sliceOdd, OddFilter).([]int)
    fmt.Println(sliceOdd) //[1 3 3 5 5]
 
}

輸出結果

[2 2 4 4 6]  
[1 3 3 5 5]

四 總結上面例子中閉包的使用有點相似於面向對象設計模式中的模版模式,在模版模式中是在父類中定義公共的行爲執行序列,而後子類經過重載父類的方法來實現特定的操做,而在Go語言中咱們使用閉包實現了一樣的效果。其實理解閉包最方便的方法就是將閉包函數當作一個類,一個閉包函數調用就是實例化一個類(在Objective-c中閉包就是用類來實現的),而後就能夠從類的角度看出哪些是「全局變量」,哪些是「局部變量」。例如在第一個例子中,pos和neg分別實例化了兩個「閉包類」,在這個「閉包類」中有個「閉包全局變量」sum。因此這樣就很好理解返回的結果了。

相關文章
相關標籤/搜索