函數聲明包括函數名、形式參數列表、返回值列表(可省略)以及函數體。數組
func function-name(param...) (result...) { body }
形式參數列表描述了函數的參數名以及參數類型。這些參數做爲局部變量,其值由參數調用者提供。返回值列表描述了函數返回值的變量名以及類型。若是函數返回一個無名變量或者沒有返回值,返回值列表的括號是能夠省略的。若是一個函數聲明不包括返回值列表,那麼函數體執行完畢後,不會返回任何值。數據結構
func hypot(x, y float64) float64 { return math.Sqrt(x*x + y*y) } fmt.Println(hypot(3,4)) // "5"
函數能夠是遞歸的,這意味着函數能夠直接或間接的調用自身。對許多問題而言,遞歸是一種強有力的技術,例如處理遞歸的數據結構。函數
func a(i int) (res int){ if i == 1 { return i } return i * a(i - 1) } fmt.Println(a(5)) // 120
在Go中,一個函數能夠返回多個值。google
func calculation(a,b int)(add,sub int) { add = a + b sub = a - b }
在Go中有一部分函數老是能成功的運行,對各類可能的輸入都作了良好的處理,使得運行時幾乎不會失敗,除非遇到災難性的、不可預料的狀況,好比運行時的內存溢出。致使這種錯誤的緣由很複雜,難以處理,從錯誤中恢復的可能性也很低。指針
panic是來自被調函數的信號,表示發生了某個已知的bug。一個良好的程序永遠不該該發生panic異常。日誌
value, ok := cache.Lookup(key) if !ok { // ...cache[key] does not exist… }
當一次函數調用返回錯誤時,調用者有應該選擇什麼時候的方式處理錯誤。根據狀況的不一樣,有不少處理方式.code
resp,err := http.Get("https://www.google.com") if err != nil { fmt.Println(err) }
函數常常會返回多種錯誤,這對終端用戶來講可能會頗有趣,但對程序而言,這使得狀況變得複雜。不少時候,程序必須根據錯誤類型,做出不一樣的響應。讓咱們考慮這樣一個例子:從文件中讀取n個字節。若是n等於文件的長度,讀取過程的任何錯誤都表示失敗。若是n小於文件的長度,調用者會重複的讀取固定大小的數據直到文件結束。這會致使調用者必須分別處理由文件結束引發的各類錯誤。基於這樣的緣由,io包保證任何由文件結束引發的讀取失敗都返回同一個錯誤——io.EOF,該錯誤在io包中定義:遞歸
package io import "errors" // EOF is the error returned by Read when no more input is available. var EOF = errors.New("EOF")
調用者只需經過簡單的比較,就能夠檢測出這個錯誤。下面的例子展現瞭如何從標準輸入中讀取字符,以及判斷文件結束。內存
in := bufio.NewReader(os.Stdin) for { r, _, err := in.ReadRune() if err == io.EOF { break // finished reading } if err != nil { return fmt.Errorf("read failed:%v", err) } // ...use r… }
由於文件結束這種錯誤不須要更多的描述,因此io.EOF有固定的錯誤信息——「EOF」。對於其餘錯誤,咱們可能須要在錯誤信息中描述錯誤的類型和數量,這使得咱們不能像io.EOF同樣採用固定的錯誤信息。在7.11節中,咱們會提出更系統的方法區分某些固定的錯誤值。input
在Go中,函數被看做第一類值(first-class values):函數像其餘值同樣,擁有類型,能夠被賦值給其餘變量,傳遞給函數,從函數返回。對函數值(function value)的調用相似函數調用。
func add(a,b int) (sum int) { sum = a + b } func main() { f = add fmt.Println(sum(1,2)) }
函數類型的零值是nil。調用值爲nil的函數值會引發panic錯誤:
var f func(int) int f(3) // 此處f的值爲nil, 會引發panic錯誤
擁有函數名的函數只能在包級語法塊中被聲明,經過函數字面量(function literal),咱們可繞過這一限制,在任何表達式中表示一個函數值。函數字面量的語法和函數聲明類似,區別在於func關鍵字後沒有函數名。函數值字面量是一種表達式,它的值被成爲匿名函數(anonymous function)。
func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" }
參數數量可變的函數稱爲爲可變參數函數。典型的例子就是fmt.Printf和相似函數。Printf首先接收一個的必備參數,以後接收任意個數的後續參數。
在聲明可變參數函數時,須要在參數列表的最後一個參數類型以前加上省略符號「...」,這表示該函數會接收任意數量的該類型參數。
func main() { fmt.Println(sum(1,2,3,4,5)) // 15 var sli = []int{1,2,3,4,5} fmt.Println(sli) // [1 2 3 4 5] fmt.Println(sum(sli...)) // 15 } func sum(values ...int) int { sum := 0 for _, v := range values { sum += v } return sum }
Go的類型系統會在編譯時捕獲不少錯誤,但有些錯誤只能在運行時檢查,如數組訪問越界、空指針引用等。這些運行時錯誤會引發painc異常。
通常而言,當panic異常發生時,程序會中斷運行,並執行此goroute上的defer函數。
當某些不該該發生的場景發生時,咱們就應該調用panic。
name := "zhaohaiyu" switch name { case "zhy": fmt.Println("zhy") case "haiyuzhao": fmt.Println("haiyuzhao") default: panic("沒有這個名字") }
雖然Go的panic機制相似於其餘語言的異常,但panic的適用場景有一些不一樣。因爲panic會引發程序的崩潰,所以panic通常用於嚴重錯誤,如程序內部的邏輯不一致。
一般來講,不該該對panic異常作任何處理,但有時,也許咱們能夠從異常中恢復,至少咱們能夠在程序崩潰前,作一些操做。
若是在deferred函數中調用了內置函數recover,而且定義該defer語句的函數發生了panic異常,recover會使程序從panic中恢復,並返回panic value。致使panic異常的函數不會繼續運行,但能正常返回。在未發生panic時調用recover,recover會返回nil。
讓咱們以語言解析器爲例,說明recover的使用場景。考慮到語言解析器的複雜性,即便某個語言解析器目前工做正常,也沒法確定它沒有漏洞。所以,當某個異常出現時,咱們不會選擇讓解析器崩潰,而是會將panic異常看成普通的解析錯誤,並附加額外信息提醒用戶報告此錯誤。
defer func () { if p := recover(); p != nil { fmt.Println(p) // 主動拋錯 // 能夠進行寫日誌等操做 } }() panic("主動拋錯")