在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。
函數值也成爲閉包,它是由函數和引用環境構成
首先說區別:
匿名函數的應用場景:
下面運用匿名函數,作一個課程的拓撲排序, 返回整個課程的學習順序:(學數據結構以前先要學習離散數學才行等,注意:有些課程能夠並行學習的,這個順序能夠隨機化,先不用管)
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在詞法域上是找不到它的聲明的。賦值時可使用它