Go語言開發(六)、Go語言閉包

Go語言開發(六)、Go語言閉包

1、函數式編程

一、函數式編程簡介

函數式編程是一種編程模型,將計算機運算看做是數學中函數的計算,而且避免了狀態以及變量的概念。
在面向對象思想產生前,函數式編程已經有數十年的歷史。隨着硬件性能的提高以及編譯技術和虛擬機技術的改進,一些曾被性能問題所限制的動態語言開始受到關注,Python、Ruby和Lua等語言都開始在應用中嶄露頭角。動態語言因其方便快捷的開發方式成爲不少人喜好的編程語言,伴隨動態語言的流行,函數式編程也開始流行。編程

二、函數式編程的特色

函數式編程的主要特色以下:
A、變量的不可變性: 變量一經賦值不可改變。若是須要改變,則必須複製出去,而後修改。
B、函數是一等公民: 函數也是變量,能夠做爲參數、返回值等在程序中進行傳遞。
C、尾遞歸:若是遞歸很深的話,堆棧可能會爆掉,並致使性能大幅度降低。而尾遞歸優化技術(須要編譯器支持)能夠在每次遞歸時重用stack。 數組

三、高階函數

在函數式編程中,函數須要做爲參數傳遞,即高階函數。在數學和計算機科學中,高階函數是至少知足下列一個條件的函數:
A、函數能夠做爲參數被傳遞
B、函數能夠做爲返回值輸出安全

2、匿名函數

一、匿名函數簡介

匿名函數是指不須要定義函數名的一種函數實現方式,匿名函數由一個不帶函數名的函數聲明和函數體組成。C和C++不支持匿名函數。網絡

func(x,y int) int {
    return x + y
}

二、匿名函數的值類型

在Go語言中,全部的函數是值類型,便可以做爲參數傳遞,又能夠做爲返回值傳遞。
匿名函數能夠賦值給一個變量:閉包

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

定義一種函數類型:
type CalcFunc func(x, y int) int
函數能夠做爲值傳遞:app

func AddFunc(x, y int) int {
return x + y
}

func SubFunc(x, y int) int {
   return x - y
}

...

func OperationFunc(x, y int, calcFunc CalcFunc) int {
   return calcFunc(x, y)
}

func main() {
   sum := OperationFunc(1, 2, AddFunc)
   difference := OperationFunc(1, 2, SubFunc)
   ...
}

函數能夠做爲返回值:編程語言

// 第一種寫法
func add(x, y int) func() int {
   f := func() int {
      return x + y
   }
   return f
}

// 第二種寫法
func add(x, y int) func() int {
   return func() int {
      return x + y
   }
}

當函數返回多個匿名函數時建議採用第一種寫法:ide

func calc(x, y int) (func(int), func()) {
   f1 := func(z int) int {
      return (x + y) * z / 2
   }

   f2 := func() int {
      return 2 * (x + y)
   }
   return f1, f2
}

匿名函數的調用有兩種方法:函數式編程

// 經過返回值調用
func main() {
   f1, f2 := calc(2, 3)
   n1 := f1(10)
   n2 := f1(20)
   n3 := f2()
   fmt.Println("n1, n2, n3:", n1, n2, n3)
}

// 在匿名函數定義的同時進行調用:花括號後跟參數列表表示函數調用
func safeHandler() {
   defer func() {
      err := recover()
      if err != nil {
         fmt.Println("some exception has happend:", err)
      }
   }()
   ...
}

3、閉包

一、閉包的定義

函數能夠嵌套定義(嵌套的函數通常爲匿名函數),即在一個函數內部能夠定義另外一個函數。Go語言經過匿名函數支持閉包,C++不支持匿名函數,在C++11中經過Lambda表達式支持閉包。
閉包是由函數及其相關引用環境組合而成的實體(即:閉包=函數+引用環境)。
閉包只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,函數代碼在函數被定義後就肯定,不會在執行時發生變化,因此一個函數只有一個實例。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。
所謂引用環境是指在程序執行中的某個點全部處於活躍狀態的約束所組成的集合。約束是指一個變量的名字和其所表明的對象之間的聯繫。因爲在支持嵌套做用域的語言中,有時不能簡單直接地肯定函數的引用環境,所以須要將引用環境與函數組合起來。函數

二、閉包的本質

閉包是包含自由變量的代碼塊,變量不在代碼塊內或者任何全局上下文中定義,而是在定義代碼塊的環境中定義。因爲自由變量包含在代碼塊中,因此只要閉包還被使用,那麼自由變量以及引用的對象就不會被釋放,要執行的代碼爲自由變量提供綁定的計算環境。
閉包能夠做爲函數對象或者匿名函數。支持閉包的多數語言都將函數做爲第一級對象,即函數能夠存儲到變量中做爲參數傳遞給其它函數,可以被函數動態建立和返回。

func add(n int) func(int) int {
   sum := n
   f := func(x int) int {
      var i int = 2
      sum += i * x
      return sum
   }
   return f
}

add函數中函數變量爲f,自由變量爲sum,同時f爲sum提供綁定的計算環境,sum和f組成的代碼塊就是閉包。add函數的返回值是一個閉包,而不只僅是f函數的地址。在add閉包函數中,只有內部的匿名函數f才能訪問局部變量i,而沒法經過其它途徑訪問,所以閉包保證了i的安全性。
當分別用不一樣的參數(10, 20)注入add函數而獲得不一樣的閉包函數變量時,獲得的結果是隔離的,即每次調用add函數後都將生成並保存一個新的局部變量sum。
在函數式語言中,當內嵌函數體內引用到體外的變量時,將會把定義時涉及到的引用環境和函數體打包成一個總體(閉包)返回。
當每次調用add函數時都將返回一個新的閉包實例,不一樣實例之間是隔離的,分別包含調用時不一樣的引用環境現場。不一樣於函數,閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。
從形式上看,匿名函數都是閉包。
函數只是一段可執行代碼,編譯後就固定,每一個函數在內存中只有一份實例,獲得函數的入口點即可以執行函數。
對象是附有行爲的數據,而閉包是附有數據的行爲。

三、閉包的使用

閉包常常用於回調函數,當IO操做(例如從網絡獲取數據、文件讀寫)完成的時候,會對獲取的數據進行某些操做,操做能夠交給函數對象處理。
除此以外,在一些公共的操做中常常會包含一些差別性的特殊操做,而差別性的操做能夠用函數來進行封裝。

package main

import "fmt"

func adder() func(int) int {
   sum := 0
   f := func(x int) int {
      sum += x
      return sum
   }
   return f
}

func main() {
   sum := adder()
   for i := 0; i < 10; i++ {
      fmt.Println(sum(i))
   }
}

4、閉包的應用

package main

import "fmt"

//普通閉包
func adder() func(int) int {
   sum := 0
   return func(v int) int {
      sum += v
      return sum
   }
}

//無狀態、無變量的閉包
type iAdder func(int) (int, iAdder)
func adder2(base int) iAdder {
   return func(v int) (int, iAdder) {
      return base + v, adder2(base + v)
   }
}

//使用閉包實現斐波那契數列
func Fibonacci() func() int {
   a, b := 0, 1
   return func() int {
      a, b = b, a+b
      return a
   }
}

func main() {
   //普通閉包調用
   a := adder()
   for i := 0; i < 10; i++ {
      var s int =a(i)
      fmt.Printf("0 +...+ %d = %d\n",i, s)
   }
   //狀態 無變量的閉包 調用
   b := adder2(0)
   for i := 0; i < 10; i++ {
      var s int
      s, b = b(i)
      fmt.Printf("0 +...+ %d = %d\n",i, s)
   }

   //調用斐波那契數列生成
   fib:=Fibonacci()
   fmt.Println(fib(),fib(),fib(),fib(),fib(),fib(),fib(),fib())
}
相關文章
相關標籤/搜索