golang函數

1.函數聲明

函數聲明包括函數名、形式參數列表、返回值列表(可省略)以及函數體。數組

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"

2.遞歸

函數能夠是遞歸的,這意味着函數能夠直接或間接的調用自身。對許多問題而言,遞歸是一種強有力的技術,例如處理遞歸的數據結構。函數

func a(i int) (res int){
    if i == 1 {
        return i 
    }
    return i * a(i - 1)
}

fmt.Println(a(5)) // 120

3.多返回值

在Go中,一個函數能夠返回多個值。google

func calculation(a,b int)(add,sub int) {
	add = a + b
    sub = a - b
}

4.錯誤

在Go中有一部分函數老是能成功的運行,對各類可能的輸入都作了良好的處理,使得運行時幾乎不會失敗,除非遇到災難性的、不可預料的狀況,好比運行時的內存溢出。致使這種錯誤的緣由很複雜,難以處理,從錯誤中恢復的可能性也很低。指針

panic是來自被調函數的信號,表示發生了某個已知的bug。一個良好的程序永遠不該該發生panic異常。日誌

value, ok := cache.Lookup(key)
if !ok {
    // ...cache[key] does not exist…
}

4.1. 錯誤處理策略

當一次函數調用返回錯誤時,調用者有應該選擇什麼時候的方式處理錯誤。根據狀況的不一樣,有不少處理方式.code

resp,err := http.Get("https://www.google.com")
if err != nil {
    fmt.Println(err)
}

4.2. 文件結尾錯誤(EOF)

函數常常會返回多種錯誤,這對終端用戶來講可能會頗有趣,但對程序而言,這使得狀況變得複雜。不少時候,程序必須根據錯誤類型,做出不一樣的響應。讓咱們考慮這樣一個例子:從文件中讀取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

5.函數值

在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錯誤

6.匿名函數

擁有函數名的函數只能在包級語法塊中被聲明,經過函數字面量(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"
}

7.可變參數

參數數量可變的函數稱爲爲可變參數函數。典型的例子就是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
}

8.Panic異常

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通常用於嚴重錯誤,如程序內部的邏輯不一致。

9.Recover捕獲異常

一般來講,不該該對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("主動拋錯")
相關文章
相關標籤/搜索