《Go語言圣經》函數值、匿名函數遞歸與可變參數
函數值(Function Values)
在 Go 語言中,函數被視為第一類值(first-class values),這意味著它們可以像其他值一樣被操作:擁有類型、賦值給變量、作為參數傳遞給其他函數或作為返回值。函數值的調用方式與普通函數相同。
func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }f := square
fmt.Println(f(3)) // 輸出: 9f = negative
fmt.Println(f(3)) // 輸出: -3
fmt.Printf("%T\n", f) // 輸出: func(int) intf = product // 編譯錯誤: 無法將 func(int, int) int 賦值給 func(int) int
函數類型的零值是 nil
,調用值為 nil
的函數會導致 panic 錯誤:
var f func(int) int
f(3) // 此處 f 為 nil,會觸發 panic
函數值可以與 nil
比較,但函數值之間不可比較,也不能作為 map 的鍵:
var f func(int) int
if f != nil {f(3)
}
匿名函數遞歸(Recursive Anonymous Functions)
當需要定義遞歸調用的匿名函數時,必須先聲明變量并指定類型,再將匿名函數賦值給該變量。這是因為 Go 編譯器需要在函數體內部解析函數類型。
以下示例展示了如何使用遞歸匿名函數進行拓撲排序(Topological Sort):
// prereqs 記錄了每個課程的前置課程
var prereqs = map[string][]string{"algorithms": {"data structures"},"calculus": {"linear algebra"},"compilers": {"data structures","formal languages","computer organization",},"data structures": {"discrete math"},"databases": {"data structures"},"discrete math": {"intro to programming"},"formal languages": {"discrete math"},"networks": {"operating systems"},"operating systems": {"data structures", "computer organization"},"programming languages": {"data structures", "computer organization"},
}func main() {for i, course := range topoSort(prereqs) {fmt.Printf("%d:\t%s\n", i+1, course)}
}func topoSort(m map[string][]string) []string {var order []stringseen := make(map[string]bool)// 聲明遞歸函數類型var visitAll func(items []string)// 賦值匿名函數visitAll = func(items []string) {for _, item := range items {if !seen[item] {seen[item] = truevisitAll(m[item]) // 遞歸調用order = append(order, item)}}}var keys []stringfor key := range m {keys = append(keys, key)}sort.Strings(keys)visitAll(keys)return order
}
關鍵點:
- 必須先聲明
visitAll
變量并指定類型func(items []string)
- 再將匿名函數賦值給
visitAll
- 函數體內部可正確解析
visitAll
的類型
可變參數(Variadic Functions)
可變參數函數可以接收任意數量的指定類型參數,在參數列表最后一個類型前加 ...
表示:
func sum(vals ...int) int {total := 0for _, val := range vals {total += val}return total
}fmt.Println(sum()) // 輸出: 0
fmt.Println(sum(3)) // 輸出: 3
fmt.Println(sum(1, 2, 3, 4)) // 輸出: 10
在函數體內部,可變參數被視為切片類型(如 []int
)。若要傳遞現有切片給可變參數函數,需在切片后加 ...
:
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // 輸出: 10
可變參數函數與以切片為參數的函數類型不同:
func f(...int) {}
func g([]int) {}fmt.Printf("%T\n", f) // 輸出: func(...int)
fmt.Printf("%T\n", g) // 輸出: func([]int)
可變參數函數常用于格式化字符串,例如:
func errorf(linenum int, format string, args ...interface{}) {fmt.Fprintf(os.Stderr, "Line %d: ", linenum)fmt.Fprintf(os.Stderr, format, args...)fmt.Fprintln(os.Stderr)
}linenum, name := 12, "count"
errorf(linenum, "undefined: %s", name) // 輸出: Line 12: undefined: count
其中 interface{}
表示最后一個參數可接收任意類型。