「Golang成長之路」函數語言程式設計

ice_moss發表於2021-08-11

一、介紹

特點:
  1. 在go語言中,函式是一等公民,可以作為引數,變數,返回值來使用
  2. 高階函式(在函式中定義函式,傳入函式,返回函式)
  3. 閉包

附加:正統式函數語言程式設計
1.不可變性:不能有狀態,只能有常量和函式
2.函式只有一個引數
對於正統式函數語言程式設計(較為複雜,閱讀性不高)我們不做太多介紹

好下面我們看一個例子:

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

這裡我們寫了一個求和的函式(這裡將匿名函式func(v int)作為返回值,並將匿名函式func的定義直接寫在函式adder的return後面)。

接著看下面:
我們在main函式中測試一下

package main

import "fmt"

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

func main(){
    a := adder()
    fmt.Println(a(1))
    fmt.Println(a(2))
    fmt.Println(a(9))
    }
 //輸出結果為:1、3、12

看到這裡是不是有點模糊了,沒事我來解釋一遍:
首先 a := adder 是將adder()的返回值給到a
再仔細看adder的返回值是一個匿名函式,所以a就相當於函式
func(v int) int{}的例項,a(1) 、 a(2) 、a(3)自然就能理解了,就是對應v

是不是不明白輸出結果,下面就來介紹一下閉包

閉包:

定義:Go語言中閉包是引用了自由變數的函式,被引用的自由變數和函式一同存在,即使已經離開了自由變數的環境也不會被釋放或者刪除,在閉包中可以繼續使用這個自由變數,因此,簡單的說:函式 + 引用環境 = 閉包
我的理解:就是指函式在第一次呼叫時的返回值,返回後,其返回值不會立即被刪除(儲存在某個地方),當下一次呼叫該函式時會將上一次的返回值作為這次呼叫的引數;例如上面的求和函式:

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

func main(){
 a := adder()
 fmt.Println(a(1))  //第一次呼叫時sum = 0 , sum = 0 + 1,返回值為1
 fmt.Println(a(2)) //第二次呼叫時sum = 1 ,sum = 1 +2,返回值為3
 fmt.Println(a(9))  //第三次呼叫時sum = 9,sum = 3 + 9,返回值為12
 }

這是不是看懂了閉包

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

func main() {
   a := adder()
   for i := 0; i <= 5; i++{
      fmt.Println(a1(i))
   }
   //輸出結果為:0、1、3、6、10、15

二、函數語言程式設計的應用

1、斐波那契數列
數列:0、1、1、2、3、5、8、13、21……
前兩個數之和是第三個數,0+1 =1、1+1=2、1+2=3、2+3=5、3+5=8、5+8=12……
這裡我們需要使用函數語言程式設計的模式將其實現

package main

func fibonacci() func() int{
  a :=0, b := 1
    return func() int{
    a , b = b, a+b
    return a
    }
}

func main(){
    f := fibonacci()

    fmt.Println(f())  //1
    fmt.Println(f())  //1
    fmt.Println(f())  //2
    fmt.Println(f())  //3
    fmt.Println(f())  //5
    fmt.Println(f())  //8
}

斐波那契數列生成器:
這裡我們需要像讀取檔案一樣,就需要實現read的介面
我們將列印函式拿過來:

func printContentFile(reader io.Reader) {
    scanner := bufio.NewScanner(reader)
    for scanner.Scan() {    //
        println(scanner.Text())
    }
}

x想要直接呼叫printContentFile()自動生成斐波那契數,就必須實現read介面,定義一個type:函式。使其實現Reader介面。

//函式實現介面
//定義一個函式,使用type修飾。可以實現介面,也就是說只要是被type修飾的東西都可以實現介面。

type IntGen func() int
//實現read介面
func (g IntGen) Read(p []byte) (n int, err error) {
    next := g()

    if next > 10000 { //這裡因為不設定退出會一直列印下去,所以做了限制
        return 0, io.EOF
    }
    s := fmt.Sprintf("%d\n", next)
    return strings.NewReader(s).Read(p)
}

函式返回為fibonacci()

/**
    函式生成斐波那契數列(每個數字都是由前兩個數字相加得到)
 */
func fibonacci() IntGen {
    a, b := 0, 1
    return func() int {
        //在這裡,生成關鍵
        // 1 1 2 3 5 8
        //   a b
        //     a b
        a, b = b, a+b
        return a
    }
}

最後呼叫:

func main() {

    fun := fibonacci()

    printContentFile(fun)
}

列印結果:

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

從這個例子中可以發現,函式還可以實現介面,使用在go語言中函式是一等公民

2、函數語言程式設計實現中序遍歷 + 多業務

package tree1
import "fmt"
//結構體Node
type Node struct{
     Value int
     Left, Right *Node
}
//列印
func (node *Node)Println(){
     print(node.Value)
}
func (node *Node)Traverse(){    //中序遍歷+列印
     node.TraverseFun( func(*Node){
        node.print()
     })
     fmt.Println()
}
func (node *Node)TraverseFun(f func(*node){   //中序遍歷+……
     if node == nil{
       return
     }
node.Left.TraverseFun(f)
f(node)
node.Right.TraverseFun(f)
}
func main() {
   //向樹中新增資料
   root := tree1.Node{Value:0}
   root.Left = &tree1.Node{1, nil, nil}
   root.Right = &tree1.Node{2, nil,nil}
   root.Left.Left = &tree1.Node{3, nil,nil}
   root.Left.Right = &tree1.Node{4, nil, nil}
   root.Right.Left = &tree1.Node{5, nil,nil}
   root.Right.Left = &tree1.Node{6, nil, nil}
   //中序遍歷
   root.Traverse()

   TraverseCount := 0 //列印資料個數
   root.TraverseFunc(func(node *tree1.Node) {
      TraverseCount++
       })
    fmt.Println(TraverseCount)
}

看到是沒有問題的。且比之前的Traverse更加靈活了,在中序遍歷是可以做很多東西了

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章