閉包
- 閉包就是一個函數 “捕獲” 了和它在同一作用域的其他常量和變量
- 閉包被調用的時候雹食,不管在程序說明地方調用淹父,閉包能夠使用這些常量或者變量
- 閉包不關心這些捕獲了的變量和常量是否超出了作用域躁劣,所以只要閉包還在使用七婴,變量就還存在
- Go 語言中醒第,所有匿名函數(Go 語言規(guī)范中稱之為函數字面量)都是閉包
- 閉包和普通函數的區(qū)別是閉包沒有名字(所以
func
關鍵字后面緊接著左括號)
- 閉包的一種用法是利用包裝函數來為被包裝的函數預定義一到多個參數
- 例子(給大量文件增加不同的后綴):
addPng := func(name string) string { return name + ".png" }
addJpg := func(name string) string { return name + ".jpg" }
fmt.Println(addPng("filename"), addJpg("filename"))
- 現實環(huán)境中我們常用到一個工廠函數(factory function)供置,工廠函數返回一個函數:
func MakeAddSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
遞歸函數
- 遞歸函數就是調用自己的函數
- 互相遞歸函數就是互相調用對方的函數
- 遞歸函數通常有相同的結構:
一個跳出條件
和 一個遞歸體
- 跳出條件就是一個條件語句况凉,例如
if
語句谚鄙,根據傳入的參數判斷是否需要停止遞歸
- 遞歸體是函數自身所做的一些處理,包括最少也得調用自身一次(調用它互相遞歸的另一個函數體)
- 遞歸調用時所傳入的參數一定不能和當前函數傳入的一樣刁绒,在
跳出條件
里還會檢查是否可以結束遞歸
- 遞歸函數非常便于實現遞歸的數據結構例如
二叉樹
- 遞歸函數對于數值計算而言可能會性能比較低下
- 常規(guī)遞歸函數例子:
for n := 0; n < 20; n++ {
fmt.Print(Fibonacci(n), " ")
}
fmt.Println()
func Fibonacci(n int) int {
if n < 2 {
return n
}
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
females := make([]int, 20)
males := make([]int, len(females))
for n := range females {
females[n] = HofstadterFemale(n)
males[n] = HofstadterMale(n)
}
fmt.Println("F", females)
fmt.Println("M", males)
func HofstadterFemale(n int) int {
if n <= 0 {
return 1
}
return n - HofstadterMale(HofstadterFemale(n - 1))
}
func HofstadterMale(n int) int {
if n <= 0 {
return 0
}
return n - HofstadterFemale(HofstadterMale(n - 1))
}
- 函數開始處都有一個
跳出條件
用來確保遞歸能夠正常結束闷营,在遞歸發(fā)生的地方我們需要保證最終 跳出條件
會被滿足
- Go 語言不需要預定義函數,Go 語言允許函數以任何順序定義
- 能用循環(huán)的話知市,最好使用循環(huán)傻盟,循環(huán)的好處是可以減少調用的開銷
運行時選擇函數
- Go 語言中,函數屬于第一類值嫂丙,也就是說娘赴,我們可以將它保存在一個變量里,這樣我們可以決定要執(zhí)行哪一個函數
- 同一個函數可以有兩個或者多個不同的實現跟啤,在使用的時候創(chuàng)建就行了
使用映射和函數引用來制造分支
var FunctionForSuffix = map[string]func(string) ([]string, error) {
".gz": GzipFileList,
".tar": TarFileList,
".tar.ga": TarFileList,
".tgz": TarFileList,
".zip": ZipFileList,
}
func ArchiveFileListMap(file string) ([]string, error) {
if function, ok := FunctionForSuffix[Suffix(file)]; ok {
return function(file)
}
return nil, errors.New("unrecognized archive")
}
- 通過映射可以使事情變得條理清晰
- 還可以動態(tài)地往映射里增加其他的項
- 映射查詢的速度不會隨著項目的增加而降低
動態(tài)函數的創(chuàng)建
var IsPalindrome func(string) bool
func init() {
if len(os.Args) > 1 && (os.Args[1] == "-a" || os.Args[1] == "--ascii") {
os.Args = append(os.Args[:1], os.Args[2:]...) // 去掉參數
IsPalindrome = func(s string) bool { // 簡單的 ASCII 版本
if len(s) <= 1 {
return true
}
if s[0] != s[len(s) - 1] {
return false
}
return IsPalindrome(s[1 : len(s) - 1])
}
} else {
IsPalindrome = func(s string) bool { // 簡單的 ASCII 版本
// 同前
}
}
}
- 實際代碼被執(zhí)行完全取決于創(chuàng)建的是哪個版本的函數