Go語言基礎之函式
在Go語言中,函式是一等公民,老牛逼了!
函式
函式定義
-
關鍵字:
func
func 函式名(引數) (返回值) { 函式體 }
-
函式名:由字母、數字、下劃線組成。但函式名的第一個字母不能是數字。在同一個包內,函式名也稱不能重名。
-
引數:引數由引數變數和引數變數的型別組成,多個引數之間使用
,
分隔。 -
返回值:返回值由返回值變數和其變數型別組成,也可以只寫返回值的型別,多個返回值必須用
()
包裹,並用,
分隔。 -
函式體:實現指定功能的程式碼塊。
函式的呼叫
-
像Python那樣去呼叫函式
func sayHello() { fmt.Println("hello, world") } func main() { sayHello() // 呼叫函式 }
引數
-
型別簡寫
如果傳入引數的型別相同,則可以省略型別,例如:
func intSum(x, y int) int { return x + y }
-
可變引數
可變引數是指函式的引數數量不固定。Go語言中的可變引數通過在引數名後加
...
來標識。注意:可變引數通常要作為函式的最後一個引數。
func intSum2(x int, y ...int) int { // 傳入多個引數,實際上是一個切片 fmt.Println(x) sum := 0 for _, v := range y { sum += v } return sum } ret1 := intSum3(100) ret2 := intSum3(100, 10) ret3 := intSum3(100, 10, 20) ret4 := intSum3(100, 10, 20, 30) fmt.Println(ret1, ret2, ret3, ret4) //100 110 130 160
返回值
-
多返回值
Go語言中支援多返回值,函式如果有多個返回值,那麼必須用
()
將返回值括起來func calc(x, y int) (int, int) { sum := x + Y sub := x - y return sum, sub }
-
返回值命名
函式定義時可以給返回值命名,並在函式體中直接使用這些變數,最後通過
return
關鍵字返回。func calc(x, y int) (sum int, sub int) { sum := x + Y sub := x - y return }
-
返回值補充
當我們的一個函式返回值型別為slice時,nil可以看做是一個有效的slice,沒必要顯示返回一個長度為0的切片。
func someFunc(x string) []int { if x == "" { return nil // 沒必要返回[]int{} } }
函式型別與變數
定義函式型別
我們可以使用type
關鍵字來定義一個函式型別,具體格式如下
type calculation func (int, int) int
// 定義了一個 calculation 型別,這是一種函式型別,它接收兩個int型別,返回int型別
凡是滿足這個條件的函式都是calculation
型別的函式,例如下面的add
和sub
都是calculation
型別
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
var c calculation
c = add
函式型別變數
我們可以宣告函式型別的變數並且為該變數賦值
func main() {
var c calculation // 宣告一個calculation型別的變數c
c = add // 把add賦值給c
fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
fmt.Println(c(1, 2)) // 像呼叫add一樣呼叫c
f := add // 將函式add賦值給變數f1
fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
fmt.Println(f(10, 20)) // 像呼叫add一樣呼叫f
}
高階函式
-
函式作為引數
func add(x, y int) int { return x + y } func calc(x, y int, op func(int, int) int) int { return op(x, y) } func main() { ret2 := calc(10, 20, add) fmt.Println(ret2) //30 }
-
函式作為返回值
func do(s string) (func(int, int) int, error) { switch s { case "+": return add, nil case "-": return sub, nil default: err := errors.New("無法識別的操作符") return nil, err } } do("+") // 返回add函式的地址和一個nil
匿名函式和閉包
匿名函式定義
func (引數) (返回值) {
函式體
}
匿名函式因為沒有函式名,所以沒辦法像普通函式那樣呼叫,所以匿名函式需要儲存到某個變數或者作為立即執行函式:
func main() {
// 將匿名函式儲存到變數
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 通過變數直接呼叫函式
// 自執行函式
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
匿名函式多用於實現回撥函式和閉包。
閉包
閉包指的是一個函式和與其相關的引用環境組合而成的實體。簡單來說,閉包=函式+引用環境
。 首先我們來看一個例子:
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60
f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
}
變數f
是一個函式並且它引用了其外部作用域中的x
變數,此時f
就是一個閉包。 在f
的生命週期內,變數x
也一直有效。
閉包進階例項1:
func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder2(10)
fmt.Println(f(10)) //20
fmt.Println(f(20)) //40
fmt.Println(f(30)) //70
f1 := adder2(20)
fmt.Println(f1(40)) //60
fmt.Println(f1(50)) //110
}
閉包進階例項2:
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(txtFunc("test")) //test.txt
}
閉包進階例項3:
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}
閉包=函式+引用環境
defer語句
Go語言中的defer
語句會將其後面跟隨的語句進行延遲處理。在defer
歸屬的函式即將返回時,將延遲處理的語句按defer
定義的逆序進行執行,也就是說,先被defer
的語句最後被執行,最後被defer
的語句,最先被執行。
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
} // 在函式執行完畢之前的最後執行,而且是倒序執行
// 輸出結果
start
end
3
2
1
由於defer
語句延遲呼叫的特性,所以defer
語句能非常方便的處理資源釋放問題。比如:資源清理、檔案關閉、解鎖及記錄時間等。
defer執行時機
在Go語言的函式中return
語句在底層並不是原子操作,它分為給返回值賦值和RET
指令兩步。而defer
語句執行的時機就在返回值賦值操作後,RET
指令執行前。具體如下圖所示:
3543.21
defer經典案例
func f1() int {
x := 5
defer func() {
x++
}()
return x
}
func f2() (x int) {
defer func() {
x++
}()
return 5
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5
}
func main() {
fmt.Println(f1()) 5
fmt.Println(f2()) 6
fmt.Println(f3()) 5
fmt.Println(f4()) 5
}
defer面試題
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
// defer巢狀,內部函式不受影響
// defer註冊要延遲執行的函式時該函式所有的引數都需要確定其值
內建函式介紹
內建函式 | 介紹 |
---|---|
close | 主要用來關閉channel |
len | 用來求長度,比如string、array、slice、map、channel |
new | 用來分配記憶體,主要用來分配值型別,比如int、struct。返回的是指標 |
make | 用來分配記憶體,主要用來分配引用型別,比如chan、map、slice |
append | 用來追加元素到陣列、slice中 |
panic和recover | 用來做錯誤處理 |
panic/recover
func funcA() {
fmt.Println("func A")
}
func funcB() {
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
func A
panic: panic in B
goroutine 1 [running]:
main.funcB(...)
.../code/func/main.go:12
main.main()
.../code/func/main.go:20 +0x98
程式執行期間funcB
中引發了panic
導致程式崩潰,異常退出了。這個時候我們就可以通過recover
將程式恢復回來,繼續往後執行。
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
//如果程式出出現了panic錯誤,可以通過recover恢復過來
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}