函數functionpython
- Go函數不支持 嵌套、重載和默認參數golang
- 但支持如下特性:數組
無需聲明原形、不定長變參、多返回值、命令返回值參數、匿名函數、閉包閉包
- 定義函數使用關鍵字func,且大括號不能另起一行(全部有大括號的均遵循此原則)併發
- 函數也能夠做爲一種類型的使用,直接賦值給變量(匿名函數)app
定義一個函數ide
格式:func name( 傳入的變量1 類型,變量2 類型 ) [ 返回變量 類型,變量 類型 ]{ }模塊化
- 傳入的變量能夠沒有,也可使多個函數
- 當傳入的變量類型相同時,能夠所有省略只留最後一個this
func a(a,b,c int) {}
- 返回值能夠有多個,返回值類型相同,也能夠只留最後一個,其中返回變量名稱能夠省略,省略的話,就須要每返回一個寫一個變量的類型了,若是指定了返回某個局部變量,那麼這個變量就已經被定義,那麼在函數體內便可直接使用。
- 不指定返回變量名稱,那麼須要在函數尾部寫入 return 變量1,變量2, 若是指定了返回的變量名,那麼只須要寫上return便可。
- 傳入的參數個數,也能夠不定(不定長變參),使用...來表示,在函數體內存儲這些數據的類型爲slice
func A(a ...int) -->...int必須放在最後
- 若是傳入的值有1個string,有n個int,那麼只能 fun A(b string, a ...int)這種形式接受
- 若是傳入的參數是一個常規的int、string等類型的話,屬於值傳遞(默認),即只是值得拷貝,而若是傳遞sllice,則是引用傳遞(其實slice也屬於值拷貝,只不過,slice拷貝的是內存地址。而直接修改內存地址會影響源數據)
- 若是須要把int、string類型的值傳入並修改,那麼就須要把這些類型的變量的內存地址傳入
package main import "fmt" func main() { a := 2 A(a) fmt.Println(a) } func A(a int) { i := 3 fmt.Println(i) } 結果: 3 2
把變量a的地址傳入到函數中
package main import "fmt" func main() { a := 2 A(&a) //&a表示取a的內存地址 fmt.Println(a) } func A(a *int) { //定義指針類型,指向a的內存地址 *a = 3 //直接對內存地址進行賦值 fmt.Println(*a) } 結果: 3 3
參數傳遞(傳值與傳指針)
函數的參數傳遞分爲兩種,值傳遞,和引用傳遞,值傳遞指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中若是對參數進行修改,將不會影響到實際參數。默認狀況下,GO是值傳遞,在調用過程當中不會影響到實際參數。
變量在內存中是存放於必定的地址上的,修改變量實際是修改變量地址處的內存。只有讓局部函數知道參數的內存地址,才能修改變量的值。因此引用傳遞的時候須要把變量的內存地址傳入到局部函數內(&a,&表示傳遞變量的內存地址),並將函數的參數類型調整爲*int,即改成指針類型,才能在函數中修改變量的值,此時參數仍然是copy傳遞的,只不過copy的是一個指針。
函數做爲其餘變量的值
在Go語言中,一切皆類型,函數也能夠被命名爲變量,而後對變量進行函數的調用
package main import "fmt" func main() { a := A a() } func A() { fmt.Println("Func A") } 結果: Func A
匿名函數
在定義函數的時候不指定函數的名稱,而是把函數直接賦值給某個變量的函數叫作匿名函數,調用這個函數的時候,直接使用變量的名稱便可。(由於golang中的func不支持函數嵌套,使用匿名函數能夠達到嵌套的效果) 匿名函數不能做爲頂級函數(最外層)
package main import "fmt" func main() { a := func() { fmt.Println("Func") } a() }
閉包函數
所謂閉包函數就是將整個函數的定義一鼓作氣寫好並賦值給一個變量。而後用這個變量名做爲函數名去調用函數體。閉包函數對它外層的函數中的變量具備訪問和修改的權限
package main import "fmt" func main() { f := closure(10) //調用閉包函數並傳遞10 fmt.Println(f(1)) //傳遞1給返回的函數,10+1=11 fmt.Println(f(2)) //傳遞2給返回的函數,10+2=12 } func closure(x int) func(int) int { //定義一個函數接收一個參數x,返回值也是一個函數接收一個變量y return func(y int) int { //返回一個int,函數接收一個參數,返回x+y的值 return x + y } } 結果: 11 12
defer
- 執行方式相似其餘語言中的析構函數,在函數體執行結束後按照調用順序的相反順序逐個執行 (相似於棧的方式,先進後出,後進先出)
- 即便函數發生了嚴重錯誤也會執行
- 支持匿名函數的調用
- 經常使用語資源清理、文件關閉、解鎖以及記錄時間等操做
- 經過與匿名函數配合可在return以後修改函數計算結果
- 若是函數體內某個變量做爲defer時匿名函數的參數,則在定義defer時已經得到了拷貝,不然則是引用某個變量的地址
- Go沒有異常機制,但有panic/recover模式來處理錯誤
- Panic能夠再任何地方引起,但recover只有在defer調用的函數中有效
例子
package main import "fmt" func main() { fmt.Println("a") defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") } 結果: a 3 2 1 能夠看到,在程序執行完畢後,defer是從最後一條語句開始執行的,證實了defer相似棧的運行方式
defer搭配循環的結果
package main import "fmt" func main() { for i := 0; i < 3; i++ { defer fmt.Println(i) } } 結果: 2 1 0
panic/recover實例
主要用來對程序的控制,而且僅針對函數級別的錯誤進行收集與回調。使程序能繼續運行下去
package main import "fmt" func main() { A() B() C() } func A() { fmt.Println("A") } func B() { defer func() { //這裏定義defer執行一個匿名函數,用於捕捉panic,這裏若是把defer放在panic以後那麼程序執行到panic後就會崩潰,那麼defer就不會生效 if err := recover(); err != nil { //對引起的panic進行判斷,因爲手動觸發了panic併發送了信息,那麼用recover接收的異常返回值就要不爲空,若是爲nil表示沒有異常,不爲nil就表示異常了,這裏對recover的返回值進行判斷 } }() panic("this is painc")//發送異常,異常信息爲」this is panic「 } func C() { fmt.Println("C") } 結果: A C 因爲在函數B中定義了異常的recover機制,因此不會迫使程序退出,會繼續執行
panic/recover 實例2
package main import "fmt" func main() { fmt.Println("1") fmt.Println("2") f := func() { defer func() { if err := recover(); err != nil { fmt.Println("panic") } }() panic("hello world") fmt.Println("7") } f() fmt.Println("8") } 結果: 1 2 panic //打印panic說明程序已經成功的捕捉到了異常 8
定義了匿名函數,並賦值給了變量f,匿名函數中的"7"不會打印,由於執行到panic已經崩潰了,而咱們在匿名函數內定義了recover捕捉,因此匿名函數會被退出,而後繼續執行其餘程序
擴展:
在go語言中是沒有異常捕獲機制的,經過panic/recover來實現錯誤的捕獲以及處理,利用go函數多返回值的概念,來進行check,若是err等於nil表示沒有發生錯誤,當程序發生比較嚴重的錯誤,嚴重到沒法彌補,好比索引越界,因爲咱們不能準確的判斷元素的個數,因此recover也沒有意義,因此說這個時候就是一個panic。若是知道可能會索引越界,而且但願程序能從錯誤中回覆回來,那麼這時候就須要用到recover,一旦調用recover,系統就會認爲你須要從panic狀態恢復過來,當程序進入panic狀態,那麼正常的程序將不會被執行,那麼須要定義defer來執行recover(),defer無論在任何狀態下,都會執行,只要把recover放在defer中,那麼無論程序發生了怎樣的錯誤,程序都會回覆過來,須要注意的是defer相似棧的模式,後進先出。在可能發生panic的程序以前,預先定義defer,不然程序運行到painc後直接崩潰了,這個時候他只會去檢查預先定義好的defer,而你放在panic以後,將會失效
例子1:
判斷奇偶數
package main import "fmt" func main() { a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} fmt.Println("the slice is ", a) fmt.Println("the odd is ", odd(a)) fmt.Println("the even is ", even(a)) } func odd(num []int) []int { var result []int for _, value := range num { if value%2 == 0 { result = append(result, value) } } return result } func even(num []int) []int { var result []int for _, value := range num { if value%2 == 0 { continue } result = append(result, value) } return result }
思路:分別對切片進行過濾,偶數功能模塊過濾一遍,挑出偶數,奇數功能模塊過濾一遍,挑出奇數。缺點,模塊複用 性差。
判斷奇偶數:
package main import ( "fmt" ) type funcation func(int) bool func odd(num int) bool { if num%2 == 0 { return false } return true } func even(num int) bool { if num%2 == 0 { return true } return false } func filter(slice []int, f funcation) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main() { a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("the slice is ", a) fmt.Println("the odd is ", filter(a, odd)) fmt.Println("the even is ", filter(a, even)) }
思路:把判斷奇偶的功能模塊化,而後再經過一個模塊調奇偶判斷模塊,而後再用main函數組織,(使用func類型,進行功能模塊的傳遞),有點,結構性強,邏輯強。