《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)) // 输出: 9
f = negative
fmt.Println(f(3)) // 输出: -3
fmt.Printf("%T\n", f) // 输出: func(int) int
f = 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 []string
seen := make(map[string]bool)
// 声明递归函数类型
var visitAll func(items []string)
// 赋值匿名函数
visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item]) // 递归调用
order = append(order, item)
}
}
}
var keys []string
for 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 := 0
for _, 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{}
表示最后一个参数可接收任意类型。