您好,登錄后才能下訂單哦!
函數function
- Go函數不支持 嵌套、重載和默認參數
- 但支持以下特性:
無需聲明原形、不定長變參、多返回值、命令返回值參數、匿名函數、閉包
- 定義函數使用關鍵字func,且大括號不能另起一行(所有有大括號的均遵循此原則)
- 函數也可以作為一種類型的使用,直接賦值給變量(匿名函數)
定義一個函數
格式:func name( 傳入的變量1 類型,變量2 類型 ) [ 返回變量 類型,變量 類型 ]{ }
- 傳入的變量可以沒有,也可以使多個
- 當傳入的變量類型相同時,可以全部省略只留最后一個
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類型,進行功能模塊的傳遞),有點,結構性強,邏輯強。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。