函數是組織好的、可重複使用的、用於執行指定任務的代碼塊。
Go語言中支持函數、匿名函數和閉包,而且函數在Go語言中屬於「一等公民」。數據庫
Go語言中定義函數使用func關鍵字,具體格式以下:編程
func funcName(parametername type) (output type) { //這裏是處理邏輯代碼 //返回多個值 return valu }
()
,多個返回值時必須加()
{}
裏面的內容。能夠經過funcName(parameter)
的方式調用函數。調用有返回值的函數時,能夠不接收其返回值。數組
package main import "fmt" func main() { a := 10 b := 20 res := sum(a, b) fmt.Printf("%v + %v = %v", a, b, res) } func sum(a, b int) int { return a + b }
運行結果閉包
10 + 20 = 30
形式參數:定義函數時,用於接收外部傳入的數據,叫作形式參數,簡稱形參。
實際參數:調用函數時,傳給形參的實際的數據,叫作實際參數,簡稱實參。
函數調用:app
package main import "fmt" func main() { a := 10 b := 20 res := add(a, b) fmt.Printf("%v + %v = %v", a, b, res) } func add(a, b int) sum int { sum = a + b return }
可變參數是指函數的參數數量不固定。Go語言中的可變參數經過在參數名後加...來標識。socket
注意:可變參數一般要做爲函數的最後一個參數。函數式編程
func funcName(arg ...int) {}
arg ...int
告訴Go這個函數接受不定數量的參數。注意,這些參數的類型所有是int。在函數體中,變量arg是一個int類型的slice函數
go語言函數的參數也是存在值傳遞和引用傳遞指針
數據類型:code
按照數據類型來分:
按照數據的存儲特色來分:
值傳遞:值類型的數據傳遞爲值傳遞,傳遞的是數據的副本。修改數據,對原始數據沒有影響。
package main import "fmt" func main() { arr1 := [3]int{1, 2, 3} fmt.Println("函數調用前,數組的數據:", arr1) fun(arr1) fmt.Println("函數調用後,數組的數據:", arr1) } func fun(arr2 [3]int) { fmt.Println("函數中,數組的數據:", arr2) arr2[0] = 100 fmt.Println("函數中,修改後數據的數據:", arr2) }
運行結果
函數調用前,數組的數據: [1 2 3] 函數中,數組的數據: [1 2 3] 函數中,修改後數據的數據: [100 2 3] 函數調用後,數組的數據: [1 2 3]
package main import "fmt" func main() { slice1 := []int{1, 2, 3} fmt.Println("函數調用前,切片的數據:", slice1) fun(slice1) fmt.Println("函數調用後,切片的數據:", slice1) } func fun(slice2 []int) { fmt.Println("函數中,切片的數據:", slice2) slice2[0] = 100 fmt.Println("函數中,修改後切片的數據:", slice2) }
運行結果
函數調用前,切片的數據: [1 2 3] 函數中,切片的數據: [1 2 3] 函數中,修改後切片的數據: [100 2 3] 函數調用後,切片的數據: [100 2 3]
函數的返回值:
一個函數的執行結果,返回給函數調用處,執行結果就叫函數的返回值。
return語句:
一個函數的定義上有返回值,那麼函數中必須有return語句,將執行結果返回給函數的調用處。
函數的返回結果必須和函數定義的一致,類型、數量、順序。
func add(x, y int) (sum int) { sum = x + y return }
func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return }
-
可用來捨棄某些返回值。func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return } _, sub := calc(10, 20) //捨棄sum
做用域:變量可使用的範圍。
全局變量是定義在函數外部的變量,它在程序整個運行週期內都有效。 全部的函數均可以使用,並且共享這一份數據。
package main import "fmt" var a = 10 func main() { fmt.Println("test調用前,main中訪問a:", a) test() fmt.Println("test調用後,main中訪問a:", a) } func test() { fmt.Println("操做前,test中訪問a: ", a) a = 20 fmt.Println("操做後,test中訪問a: ", a) }
運行結果
test調用前,main中訪問a: 10 操做前,test中訪問a: 10 操做後,test中訪問a: 20 test調用後,main中訪問a: 20
一個函數內部定義的變量,就叫作局部變量
局部變量只能在定義的範圍內訪問操做
package main import "fmt" func main() { test() fmt.Println("main中訪問a:", a) //undefined: a } func test() { a := 20 fmt.Println("test中訪問a: ", a) }
運行結果
# command-line-arguments .\main.go:7:35: undefined: a
局部變量和全局變量重名,優先訪問局部變量。
package main import "fmt" var a = 100 func main() { test() } func test() { a := 20 fmt.Println("test中訪問a: ", a) }
運行結果
test中訪問a: 20
另外,if
,switch
,for
語句中聲明的變量也屬於局部變量,在代碼塊外沒法訪問。
函數也是Go語言中的一種數據類型,能夠做爲另外一個函數的參數,也能夠做爲另外一個函數的返回值。
package main import "fmt" func main() { fmt.Printf("%T\n", fun1) //fun1的類型是func(int, int) fmt.Printf("%T\n", fun2) //fun2的類型是func(int, int) int } func fun1(a, b int) { fmt.Println(a, b) } func fun2(c, d int) int { fmt.Println(c, d) return 0 }
運行結果
func(int, int) func(int, int) int
var f fun(int, int) int
上面語句定義了一個變量f,它是一個函數類型,這種函數接收兩個int類型的參數而且返回一個int類型的返回值。
全部參數和返回值符合條件的函數能夠賦值給f變量
package main import "fmt" func main() { var f func(int, int) int f = sum res := f(20, 10) fmt.Println("20 + 10 = ", res) f = sub res = f(20, 10) fmt.Println("20 - 10 = ", res) } func sum(a, b int) int { return a + b } func sub(a, b int) int { return a - b }
運行結果
20 + 10 = 30 20 - 10 = 10
匿名函數就是沒有函數名的函數
func (參數) (返回值) { 函數體 }
匿名函數由於沒有函數名,因此沒辦法像普通函數那樣調用,因此匿名函數須要保存到某個變量或者做爲當即執行函數。
package main import "fmt" func main() { // 將匿名函數保存到變量中 sum := func(a, b int) int { return a + b } // 經過變量調用匿名函數 res := sum(10, 20) fmt.Println("10 + 20 =", res) // 自執行函數,匿名函數定義完直接加()執行 func(c, d int) { fmt.Printf("%v + %v = %v\n", c, d, c+d) }(10, 20) }
運行結果
10 + 20 = 30 10 + 20 = 30
go語言支持函數式編程:
一個函數被做爲參數傳遞給另外一個函數,那麼這個函數就叫作回調函數。
回調函數並不會立刻被調用執行,它會在包含它的函數內的某個特定的時間點被「回調」(就像它的名字同樣)。
package main import "fmt" func main() { res := calc(10, 20, add) fmt.Println(res) } // add是一個func(int, int)int類型的函數,能夠做爲參數傳遞給calc函數 func add(a, b int) int { return a + b } // calc 高階函數,它有兩個int類型的參數和一個func(int, int)int函數類型的參數 // oper 回調函數,它被做爲參數傳遞給calc函數 func calc(a, b int, oper func(int, int) int) int { res := oper(a, b) return res }
運行結果
30
package main import "fmt" func main() { fun := calc("+") res := fun(10, 20) fmt.Println("10 + 20 =", res) } func sum(a, b int) int { return a + b } func sub(a, b int) int { return a - b } func calc(s string) func(int, int) int { switch s { case "+": return sum case "-": return sub default: fmt.Println("你傳的是個啥玩意!") return nil } }
運行結果
10 + 20 = 30
一個外層函數,有內層函數,該內層函數會操做外層函數的局部變量(外層函數的參數,或外層函數定義的變量),而且該內層函數做爲外層函數的返回值。
這個內層函數和外層函數的局部變量,統稱爲閉包結構。
局部變量的生命週期會發生改變。正常的局部變量隨着函數的調用而建立,隨着函數的結束而銷燬。
可是閉包結構的外層函數的局部變量並不會隨着外層函數的結束而銷燬,由於內層函數還要繼續使用。
package main import "fmt" func main() { fun := add() res := fun() fmt.Println("第一次調用,res=", res) res = fun() fmt.Println("第二次調用,res=", res) res = fun() fmt.Println("第二次調用,res=", res) } func add() func() int { i := 0 return func() int { i++ return i } }
運行結果
第一次調用,res= 1 第二次調用,res= 2 第二次調用,res= 3
defer是Go語言中的延遲執行語句,用來添加函數結束時執行的代碼,經常使用於釋放某些已分配的資源、關閉數據庫鏈接、斷開socket鏈接、解鎖一個加鎖的資源。
Go語言機制擔保必定會執行defer語句中的代碼。
package main import "fmt" func main() { a := 1 b := 2 c := 3 d := 4 //defer a++ //a++ 是一個語句,並不是函數或方法,程序報錯 defer fmt.Println("defer", a) defer fmt.Println("defer", b) defer fmt.Println("defer", c) defer fmt.Println("defer", d) fmt.Println(a) fmt.Println(b) fmt.Println(c) fmt.Println(d) }
運行結果
1 2 3 4 defer 4 defer 3 defer 2 defer 1
延遲並不只僅侷限於函數。延遲一個方法調用也是徹底合法的。
package main import "fmt" // Student 學生結構體 type Student struct { name string city string } func (s Student) hello() { fmt.Printf("我叫%v, 我來自%v。\n", s.name, s.city) } func main() { s := Student{ name: "jack", city: "北京市", } defer s.hello() fmt.Print("你們好,") }
運行結果
你們好,我叫jack, 我來自北京市
defer聲明時會先計算肯定參數的值,defer推遲執行的僅是其函數體。
package main import "fmt" func main() { a := 1 defer fun(a) a++ fmt.Println("main中的a =", a) } func fun(a int) { fmt.Println("fun中的a =", a) }
運行結果
main中的a = 2 fun中的a = 1
package main import "fmt" func main() { fmt.Println(fun1()) } func fun1() int { var i int defer func() { i++ }() return i }
運行結果
0
package main import "fmt" func main() { fmt.Println(fun2()) } func fun2() (i int) { defer func() { i++ }() return i }
運行結果
1
分析:
fun1()int
函數的返回值沒有被提早聲名,其值來自於其餘變量的賦值,而 defer 中修改的也是其餘變量(其實該 defer 根本沒法直接訪問到返回值),所以函數退出時返回值並無被修改。fun2()(i int)
函數的返回值被提早聲名,這使得 defer 能夠訪問該返回值,所以在 return 賦值返回值 i 以後,defer 調用返回值 i 並進行了修改,最後導致 return 調用 RET 退出函數後的返回值纔會是 defer 修改過的值。經典案例
package main import "fmt" func main() { fmt.Println(f1()) fmt.Println(f2()) fmt.Println(f3()) fmt.Println(f4()) } func f1() int { x := 5 defer func() { x++ // defer 訪問的是變量x,訪問不到返回值 // fmt.Println("f1函數defer中的x =", x) //6 }() return x // 返回值 = 5 //返回5 } func f2() (x int) { defer func() { x++ //defer 訪問x, 能夠訪問返回值,在RET以前,將返回值修改成6 // fmt.Println("f2函數defer中的x =", x) //6 }() return 5 // 返回值(x) = 5 //返回6 } func f3() (y int) { x := 5 defer func() { x++ // defer 訪問變量x,將變量x修改成6 // fmt.Println("f3函數defer中的x =", x) //6 }() return x // 返回值(y) = 5 //返回5 } func f4() (x int) { defer func(x int) { x++ // 這裏修改的defer時傳入的x(0),將其修改成1 // fmt.Println("f4函數defer中的x =", x) //1 }(x) // defer 語句調用時傳入x的值爲int類型的默認值0 return 5 // 返回值(x) = 5 //返回5 }
運行結果
5 6 5 5
package main import ( "fmt" // "os" ) func main() { fmt.Println("start") // panic("崩潰了") // defer和以後的語句都再也不執行 // os.Exit(1) // defer和以後的語句都再也不執行 defer fmt.Println("defer") // go func() { // panic("崩潰了") // }() // defer不被執行 // panic("崩潰了") // defer會執行,但後面的語句再也不執行 fmt.Println("over") // os.Exit(1) // defer不被執行 }
內置函數 | 介紹 |
---|---|
close | 主要用來關閉channel |
len | 用來求長度,好比string、array、slice、map、channel |
new | 用來分配內存,主要用來分配值類型,好比int、struct。返回的是指針 |
make | 用來分配內存,主要用來分配引用類型,好比chan、map、slice |
append | 用來追加元素到數組、slice中 |
panic和recover | 用來作錯誤處理 |
panic和recover
Go語言中目前是沒有異常機制,可是使用panic/recover模式來處理錯誤。 panic能夠在任何地方引起,但recover只有在defer調用的函數中有效。
package main import ( "fmt" ) func main() { fmt.Println("start") defer func() { err := recover() if err != nil { fmt.Println("recover") fmt.Println("活了") } }() panic("panic") fmt.Println("over") }
運行結果
start recover 活了