這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。### 本文是對匿名函數、高階函數、閉包、同步、延時(defer)及其他 Go 函數類型或特性的概覽。> *這篇文章是針對 Go 語言中不同的函數類型或特性的摘要總結。*>> *更為深入的探討我會在近期的文章中進行,因為那需要更多的篇幅。這隻是一個開端。*---### 命名函數一個命名函數擁有一個函數名,並且要聲明在包級範圍中——*其他函數的外部**?* ***我已經在[另一篇文章](https://blog.learngoprogramming.com/golang-funcs-params-named-result-values-types-pass-by-value-67f4374d9c0a)中對它們進行了完整的介紹***<p align="center">這是一個命名函數:Len 函數接受一個 string 類型的參數並返回一個 int 類型的值</p>---### 可變參數函數變參函數可接受任意數量的參數*?* ***我已經在[另一篇文章](https://blog.learngoprogramming.com/golang-variadic-funcs-how-to-patterns-369408f19085)中對它們進行了完整的介紹***---### 方法當你將一個函數附加到某個類型時,這個函數就成為了該類型上的一個方法。因此,它可以通過這個類型來調用。在通過類型來調用其上的某個方法時,Go 語言會將該類型(接收者)傳遞給方法。#### 樣本建立一個計數器類型並為其定義一個方法:```gotype Count intfunc (c Count) Incr() int { c = c + 1 return int(c)}```如上的方法與以下寫法有同樣的效果(但並不等價):```gofunc Incr(c Count) int```<p align="center">原理並不完全如上所示,但你可以像這樣來理解</p>#### 值傳遞當 Incr 被調用時,Count 執行個體的值會被複製一份並傳遞給 Incr。```govar c Count; c.Incr(); c.Incr()// output: 1 1```<h3 align="center"><i></i>c 的值並不會增加,因為 c 是通過值傳遞的方式傳遞給方法</i></h3>#### 指標傳遞(引用傳遞)想要改變計數器 c 的值,你需要給 Incr 方法傳入 Count 類型指標——``*Count``。```gofunc (c *Count) Incr() int { *c = *c + 1 return int(*c)}var c Countc.Incr(); c.Incur()// output: 1 2```[](https://play.golang.org/p/hGVJWPIFZG"receiver")<p align="center">在我之前的一些文章中有更多的樣本:看<a href="https://blog.learngoprogramming.com/golang-const-type-enums-iota-bc4befd096d3#c320">這裡!</a>看<a href="https://blog.learngoprogramming.com/golang-funcs-params-named-result-values-types-pass-by-value-67f4374d9c0a#638f">這裡!</a></p>---### 介面方法我們用**介面方法**的方式來重建上面的程式。先建立一個叫做 Counter 的新介面:```gotype Counter interface { Incr() int}```下面的 onApiHit 函數能使用任何擁有 `Incr() int` 方法的類型:```gofunc onApiHit(c Counter) { c.Incr()}```我們即刻使用一下這個改造版的計數器——現在你可以使用一個名副其實的計數器介面了:```godummyCounter := Count(0)onApiHit(&dummyCounter)// dummyCounter = 1```我們在 Count 類型上定義了一個 `Incr() int` 方法,因此 `onApiHit()` 方法可以通過它來增長 counter —— 我將 dummyCounter 的指標傳入了 onApiHit,否則這個計數器不會因而增長。[](https://play.golang.org/p/w0oyZjmdMA"interface method")*介面方法與普通方法的區別在於介面方法更具伸縮性、可擴充性,並且它是松耦合的。你可以利用介面方法在不同的包之間進行各自所需的實現,而不用修改 onApiHit 或是是其他方法的代碼*---### 函數是一等公民一等公民意味著 Go 語言中函數也是一種實值型別,可以像其他類型的值一樣被儲存或是傳遞。<p align="center">函數可以作為一種實值型別和其他的類型配合使用,反之亦然</p>#### 樣本以下程式通過 Crunchers 切片將一個數值序列作為參數傳遞到一個叫 ”crunch“ 的函數中去。聲明一個”使用者自訂函數類型“,它需要接收一個 int 類型的值來返回一個 int 類型的值。這意味著任何使用這種類型的代碼都可以接受一個以如下形式簽名的函數:```gotype Cruncher func(int) int```聲明一些 cruncher 類型的函數:```gofunc mul(n int) int { return n * 2}func add(n int) int { return n + 100}func sub(n int) int { return n - 1}```Crunch 是一個[可變參數函數](https://blog.learngoprogramming.com/golang-variadic-funcs-how-to-patterns-369408f19085),通過 Cruncher 類型的可變參數處理一系列的整型數:```gofunc crunch(nums []int, a ...Cruncher) (rnums []int) { // 建立一個等價的切片 rnums = append(rnums, nums...) for _, f := range a { for i, n := range rnums { rnums[i] = f(n) } } return}```聲明一個具有一些初始值的整型切片,之後對它們進行處理:```gonums := []int{1, 2, 3, 4, 5}crunch(nums, mul, add, sub)```#### 輸出:```[101 103 105 107 109]```[](https://play.golang.org/p/hNSKZAo0p6"first-class func")---### 匿名函數匿名函數即沒有名字的函數,它以[函數字面量](https://golang.org/ref/spec#Function_literals)的方式在行內進行聲明。它在實現閉包、高階函數、延時函數等特殊函數時有極大作用。#### 函數簽名命名函數:```gofunc Bang(energy int) time.Duration```匿名函數:```gofunc(energy int) time.Duration```它們有相同的函數簽名形式,所以它們可以互換著使用:```gofunc(int) time.Duration```[](https://play.golang.org/p/-az-2qBr9T"annoymous func")#### 樣本我們用匿名函數的方式重構一下上面的”函數是第一公民“單元中的 cruncher 程式。在 main 函數中聲明幾個匿名 cruncher 函數。```gofunc main() { crunch(nums, func(n int) int { return n * 2 }, func(n int) int { return n + 100 }, func(n int) int { return n - 1 })}```crunch 函數只期望接收到 Cruncher 類型的函數,並不關心它(它們)是命名函數還是匿名函數,因此以上代碼可以正常工作。為了提高可讀性,在傳入 crunch 之前你可以先將這些匿名函數賦值給變數。```gomul := func(n int) int { return n * 2}add := func(n int) int { return n + 100}sub := func(n int) int { return n - 1}crunch(nums, mul, add, sub)```[](https://play.golang.org/p/iqcumj5cka"use annoymous func")---### 高階函數高階函數可以接收或返回一個甚至多個函數。本質上來來講,它用其他函數來完成工作。下面閉包單元中的 split 函數就是一個高階函數。它的返回結果是一個 tokenizer 類型的函數。---### 閉包閉包可以記住其上下文環境中所有定義過的變數。閉包的一個好處就是隨時可以在其捕獲的環境下操作其中的變數——*小心記憶體流失!*#### 樣本聲明一個新的函數類型,它返回一個已分割的字串的下一個單詞:```gotype tokenizer func() (token string, ok bool)```下面的 split 函數是一個**高階函數**,它根據指定的分割符來分割一個字串,然後返回一個可以遍曆這個被分割的字串中所有單詞的**閉包**。*這個閉包可以使用 ”token“ 和 ”last“ 兩個在其捕獲的環境下定義的變數。*#### 小試牛刀:```goconst sentence = "The quick brown fox jumps over the lazy dog"iter := split(sentence, " ")for { token, ok := iter() if !ok { break } fmt.Println(token)}```* 在這裡,我們使用了 split 函數將一句話分割成了若干個單詞,然後得到了一個*迭代器函數*,並將它賦值給 iter 變數* 然後,我開始了一個當 iter 函數返回 false 的時候才停止的無限迴圈* 每次調用 iter 都能返回下一個單詞#### 結果:```Thequickbrownfoxjumpsoverthelazydog```[](https://play.golang.org/p/AI1_5BkO1d"closure")<p align="center">再次提示,這裡面有更詳細的描述哦~</p>---### 延時函數 (defer funcs)延時函數只在其父函數返回時被調用。多個延時函數會以棧的形式一個接一個被調用。*?* ***我在[另一篇文章](https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff)中對延時函數有詳細介紹***---### 並發函數`go func()` 會與其他 goroutines 並發執行。*goroutine 是一種輕量級的線程機制,它能使你方便快捷的安排並發體系。其中,main 函數在 main-goroutine 中執行。*#### 樣本這裡,“start” 匿名函數通過 “go” 關鍵字進行調用,不會阻塞父函數的執行:```gostart := func() { time.Sleep(2 * time.Second) fmt.Println("concurrent func: ends")}go start()fmt.Println("main: continues...")time.Sleep(5 * time.Second)fmt.Println("main: ends")```#### 輸出```main: continues...concurrent func: endsmain: ends```<p align="center"><i>如果 main 函數中沒有睡眠等阻塞調用,那麼,main 函數會終止,而不會等待並發函數執行完。</i></p>```main: continues...main: ends```[](https://play.golang.org/p/UzbtrKxBna"concurrent")---### 其他類型#### 遞迴函式你能在任意一門語言中使用遞迴函式,Go 語言中的遞迴函式實現與它們也沒有本質上的區別。然而,你可別忘了每一次的函數調用通常都會建立一個[調用棧](https://en.wikipedia.org/wiki/Call_stack#Functions_of_the_call_stack)。但在 Go 中,棧是動態,它們能根據相應函數的需要進行增減。如果你可以不使用遞迴解決手上的問題,那最好。#### 黑洞函數黑洞函數能被多次定義,並且不能用通常的方式進行調用。它們在測試解析器的時候有時會非常有用:看[這裡](https://github.com/golang/tools/blob/master/imports/imports.go#L167)```gofunc _() {}func _() {}```#### 內嵌函式Go 語言的連結器會將函數放置到可執行環境中,以便稍後在運行時調用它。與直接執行代碼相比,有時調用函數是一項昂貴的操作。所以,編譯器將函數的主體注入調用者函數中。更多的相關資料請參閱:[這裡](https://github.com/golang/proposal/blob/master/design/19348-midstack-inlining.md)、[這裡](http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html)、[這裡](https://medium.com/@felipedutratine/does-golang-inline-functions-b41ee2d743fa)和[這裡](https://github.com/golang/go/issues/17373)。#### 外部函數如果你省略掉函數體,僅僅進行函式宣告,連接器會嘗試在任何可能的地方找到這個外部函數。例如:Atan Func在[*這裡只進行了聲明*](https://github.com/golang/go/blob/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97/src/pkg/math/atan.go#L54),而後在[*這裡進行了實現*](https://github.com/golang/go/blob/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97/src/pkg/math/atan_386.s)。
via: https://blog.learngoprogramming.com/go-functions-overview-anonymous-closures-higher-order-deferred-concurrent-6799008dde7b
作者:Inanc Gumus 譯者:shockw4ver 校對:rxcai polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
1276 次點擊 ∙ 1 贊