Go聖經-學習筆記之函數值(二)

上一篇 Go聖經-學習筆記之函數和錯誤處理html

下一篇 Go聖經-學習筆記之defer和異常處理node

函數值

在Go語言中,函數被看作第一類值。函數變量的零值是nil。函數變量像其餘變量同樣,能夠賦值,能夠做爲參數傳遞給函數或者方法的形參,也能夠做爲返回參數,從函數或者方法返回。下面舉了函數的聲明、賦值:golang

// 函數類型
func(int, int) int

// 函數聲明
func Add(x int, y int) int { return x + y }

// 函數賦值, 並調用函數
a:= Add
fmt.Println(a(2,3))

// 相同函數類型的變量,才能賦值
func Square(x int) int { return x*x }

a = Square // 編譯不經過 , 由於Square和Add函數值的類型是不相同的

這裏要注意的是,函數類型與函數名字沒有任何關係,函數類型: func(xxx) xxx, 因此下面這個是編譯經過,且執行成功的。面試

func Add(x, y int) int { return x+y }
func Mul(x, y int) int { return x*y }

s:= Add
fmt.Println(s(2, 3))
s = Mul
fmt.Println(s(2, 3))

函數值是不能夠比較的,函數值只能和nil比較。因此也不能用來做爲map的key。利用函數值的概念,咱們能夠把業務邏輯和算法分離開來,這個在寫框架時,是常常用到的。這裏舉一個例子,前面咱們利用遞歸分析了網頁節點元素,這裏咱們把多叉樹的深度遍歷和訪問處理節點的業務邏輯分離開:算法

for forEachNode(node *html.Node, pre, post func(*html.Node)) {
	if pre!=nil {
		pre(node)
	}
	for child:= node.FirstChild; child != nil; child = node.NextSibliing {
		forEachNode(child, pre, post)
	}	
	if post !=nil{
		post(node)
	}
}

上面的pre和post函數值是業務處理邏輯部分,其餘都是多叉樹的深度遍歷。這樣的算法與業務邏輯徹底分離開,是框架常常用的。至少我是常常看到的。這種解耦也是很是必要的。數據結構

匿名函數

擁有函數名的函數只能在包級別聲明,若是你想要在其餘地方聲明,就須要使用到匿名函數。匿名函數和函數的顯示區別在於有無函數名。閉包

匿名函數能夠訪問完整的詞法環境,這意味着在函數內定義的匿名函數能夠引用該函數的變量app

有一個很形象的DEMO:框架

func squares() func() int {
    var x int
    return func() int {
        x++
        return x*x
    }
}

func main() {
    f := squares() // 返回函數值
    
    fmt.Println(f()) // "1"  x = 1
    fmt.Println(f()) // "4"  x = 2
    fmt.Println(f()) // "9"  x = 3
    fmt.Println(f()) // "16" x = 4
}

初次接觸匿名函數的變量,若是不知道是詞法環境的變量引用,理解起來可能有點吃力。函數值不僅是代碼,並且記錄了狀態。變量是引用變量。這裏迴歸上一篇文章提出的問題:爲何函數值不能比較?函數

由於函數值裏存在變量引用問題,它不單單是一串代碼。這樣構成了一個內存環境空間,造成了一個函數值。由於存在變量引用,因此函數值不能用來比較,不能用來作map的key。

函數值也成爲閉包,它是由函數和引用環境構成

面試題:普通函數和匿名函數的區別和應用場景

首先說區別:

  • 普通函數聲明是在包級別,然後者在函數定義內
  • 普通函數是由函數名字的,然後者沒有函數名字
  • 後者又稱閉包函數,它是由函數和引用環境構成的。

匿名函數的應用場景:

  • 在使用defer作函數退出時,資源的釋放和關閉,用匿名函數很是方便。好比:文件描述符的關閉,錯誤處理,recover接收panic等。
  • 比較小的函數體,或者函數內參數的傳遞。均可以使用短小的匿名函數。

下面運用匿名函數,作一個課程的拓撲排序, 返回整個課程的學習順序:(學數據結構以前先要學習離散數學才行等,注意:有些課程能夠並行學習的,這個順序能夠隨機化,先不用管)

package main

import "fmt"

// prereqs記錄了每一個課程的前置課程
var prereqs = map[string][]string{
    "algorithms": []string{"data structures"},
    "calculus":   []string{"linear algebra"},
    "compilers": []string{
        "data structures",
        "formal languages",
        "computer organization",
    },
    "data structures":       []string{"discrete math"},
    "databases":             []string{"data structures"},
    "discrete math":         []string{"intro to programming"},
    "formal languages":      []string{"discrete math"},
    "networks":              []string{"operating systems"},
    "operating systems":     []string{"data structures", "computer organization"},
    "programming languages": []string{"data structures", "computer organization"},
}

// 咱們採用深度遍歷
func topoSort(m map[string][]string) []string {
    var order []string
    var seen = make(map[string]bool)
    // visitAll是從集合中隨機取出一個沒有標記的元素,而後經過map找它的前置課程,深度遍歷直到前置>
課程不依賴其餘課程。
    var visitAll func([]string)
    visitAll = func(items []string) {
        for _, item := range items {
            if !seen[item] {
                seen[item] = true
                visitAll(m[item])
                order = append(order, item)
            }
        }
    }
    var keys []string
    for key := range m {
        keys = append(keys, key)
    }
    visitAll(keys)
    return order
}

func main() {
    fmt.Println(topoSort(prereqs))
    return
}

這裏注意一點,不能在聲明時使用visitAll,這樣visitAll在詞法域上是找不到它的聲明的。賦值時可使用它

相關文章
相關標籤/搜索