1. 程式人生 > >Go語言開發(六)、Go語言閉包

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

技術 iad 調用 導致 nil \n 整體 不支持 變化

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

一、函數式編程

1、函數式編程簡介

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

2、函數式編程的特點

函數式編程的主要特點如下:
A、變量的不可變性: 變量一經賦值不可改變。如果需要改變,則必須復制出去,然後修改。

B、函數是一等公民: 函數也是變量,可以作為參數、返回值等在程序中進行傳遞。
C、尾遞歸:如果遞歸很深的話,堆棧可能會爆掉,並導致性能大幅度下降。而尾遞歸優化技術(需要編譯器支持)可以在每次遞歸時重用stack。

3、高階函數

在函數式編程中,函數需要作為參數傳遞,即高階函數。在數學和計算機科學中,高階函數是至少滿足下列一個條件的函數:
A、函數可以作為參數被傳遞
B、函數可以作為返回值輸出

二、匿名函數

1、匿名函數簡介

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

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

2、匿名函數的值類型

在Go語言中,所有的函數是值類型,即可以作為參數傳遞,又可以作為返回值傳遞。
匿名函數可以賦值給一個變量:

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

定義一種函數類型:
type CalcFunc func(x, y int) int
函數可以作為值傳遞:

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
   }
}

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

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)
      }
   }()
   ...
}

三、閉包

1、閉包的定義

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

2、閉包的本質

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

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函數時都將返回一個新的閉包實例,不同實例之間是隔離的,分別包含調用時不同的引用環境現場。不同於函數,閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
從形式上看,匿名函數都是閉包。
函數只是一段可執行代碼,編譯後就固定,每個函數在內存中只有一份實例,得到函數的入口點便可以執行函數。
對象是附有行為的數據,而閉包是附有數據的行為。

3、閉包的使用

閉包經常用於回調函數,當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))
   }
}

四、閉包的應用

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())
}

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