4.1 定義
- 無須前置聲明
- 不支持命名嵌套定義
- 不支持同名重載
- 不支持默認參數(shù)
- 支持不定長變參
- 支持多返回值
- 支持命名返回值
- 支持匿名函數(shù)和閉包
- 只能判斷nil女责,不能比較
函數(shù)屬于第一類對象皮假,指可在運行期創(chuàng)建,可用作函數(shù)參數(shù)或返回值,可存入變量的實體各淀。常見用法:匿名函數(shù)
4.2 參數(shù)
- 形參:函數(shù)定義中的參數(shù)擎场,局部變量
- 實參:函數(shù)調用時所傳遞的參數(shù),函數(shù)外部對象泥耀,可以使常量,變量蛔添,表達式或函數(shù)等
全都是值拷貝傳遞痰催,pass-by-value兜辞!
變參
變參本質上是一個切片,只能接收一到多個同類型參數(shù)夸溶,且必須放在列表尾部逸吵。
4.3 返回值
命名返回值
類似參數(shù),當做局部變量使用缝裁,最后由return隱式返回扫皱。
其實沒啥必要。
4.4 匿名函數(shù)
匿名函數(shù)指沒有定義名字符號的函數(shù)压语。
在函數(shù)內(nèi)部定義匿名函數(shù)啸罢,形成嵌套效果。
好處:將大函數(shù)分解成多個相對獨立的匿名函數(shù)塊胎食,用相對簡潔的調用完成邏輯流程扰才,以實現(xiàn)框架和細節(jié)的分離。
閉包
閉包closure是在其語法上下文中引用了自由變量的函數(shù)厕怜,或者說是函數(shù)和其引用環(huán)境的組合體衩匣。
有點類似,直接引用原環(huán)境變量的指針粥航。
package main
func test() []func() {
var s []func()
for i := 0; i < 2; i++ {
s = append(s, func() {
println(&i, i)
})
}
return s
}
func main() {
for _, f := range test() {
f()
}
}
輸出為:
0xc04204c000 2
0xc04204c000 2
即琅捏,閉包通過指針引用環(huán)境變量,可能會導致其生命周期延長递雀, 甚至被分配到堆內(nèi)存柄延。此外還延遲求值。
上面的代碼中缀程,main執(zhí)行函數(shù)時搜吧,讀取的是環(huán)境變量i最后一次循環(huán)時的值。
解決方法是:每次用不同的環(huán)境變量杨凑,或傳參復制滤奈,讓各自閉包環(huán)境各不相同。
4.5 延遲調用
延遲調用注冊的是調用撩满,必須提供執(zhí)行所需參數(shù)蜒程,參數(shù)值在注冊時被復制并緩存起來,如對狀態(tài)敏感伺帘,可改用指針或閉包昭躺。
func main() {
x, y := 1, 2
defer func(a int) {
println("defer x, y = ", a, y)
}(x)
x += 100
y += 100
println(x, y)
}
輸出
101 102
defer x, y = 1 102
多個延遲注冊按FILO,先進后出次序進行曼追。
編譯器通過插入額外指令來實現(xiàn)延遲調用執(zhí)行窍仰,return和panic都會終止當前流程,引發(fā)延遲調用礼殊。
return的順序:
- 完成對函數(shù)返回值的賦值
- call defer
- 匯編ret指令
誤用
循環(huán)時defer會等到main函數(shù)結束驹吮,可能浪費資源,解決方法:匿名函數(shù)
性能
延遲調用代價更高晶伦,包括注冊調用碟狞,額外的緩存開銷,相差幾倍婚陪,所以性能要求高的算法應避免defer
4.6 錯誤處理
標準庫將error定義為借口類型族沃,以便實現(xiàn)自定義錯誤類型
type error interface {
Error() string
}
大量函數(shù)和方法返回error,代碼賊難看泌参,全是檢查語句脆淹,解決思路:
- 使用專門的檢查函數(shù)處理錯誤邏輯,簡化檢查代碼
- 不影響邏輯的情況下沽一,使用defer延后處理錯誤
- 不中斷邏輯的情況下盖溺,將錯誤作為內(nèi)部狀態(tài)保存,等最終“提交”時再處理
panic铣缠, recover
接近try/catch結構化異常
- 連續(xù)調用panic烘嘱,只有最后一個會被recover捕獲。
- 在延遲函數(shù)中panic蝗蛙,不會影響后續(xù)延遲調用執(zhí)行蝇庭,而在recover之后,可以重新panic并捕獲捡硅。
- recover必須在延遲調用函數(shù)中執(zhí)行才能正常工作
func catch() {
log.Println("catch:", recover())
}
func main() {
defer catch() //nil
defer catch() //成功
defer log.Println(recover()) //失敗
defer recover() //失斚凇!
panic("i am dead")
}
輸出
2019/05/08 10:54:30 <nil>
2019/05/08 10:54:30 catch: i am dead
2019/05/08 10:54:30 catch: <nil>
建議:除非是不可恢復性壮韭,導致系統(tǒng)無法正常工作的錯誤北发,否則不建議使用panic