在Go中,匿名函数(函数字面量,闭包)的出现是为了支持函数式风格、回调以及简洁的算法封装。它们常用于处理集合、异步任务和作为参数传递。
如果没有匿名函数,代码会变得冗长:每个处理都必须单独提取到一个命名函数中。但使用它们时也会出现问题:如何处理变量“捕获”,内存在哪里存储,以及在声明和作为参数传递时有哪些特点?如果在外部修改变量,变量捕获是否安全?一个常见错误是在循环中不正确地捕获变量。
匿名函数被声明为字面量,可以赋值给变量或立即使用。如果匿名函数引用外部区域的变量,则会将它们“捕获”并在闭包的生命周期内保存。作为函数的参数,匿名函数通常使用与签名兼容的func类型传递。大多数问题出现在捕获循环变量时——在这种情况下,如果需要将逻辑封装在闭包中,务必要在循环内部创建一个新变量。
示例代码:
func operate(nums []int, op func(int) int) []int { res := make([]int, len(nums)) for i, n := range nums { res[i] = op(n) } return res } func main() { arr := []int{1, 2, 3} out := operate(arr, func(x int) int {return x * x}) fmt.Println(out) // [1 4 9] }
关键特点:
在通过匿名函数捕获具有可变值的变量时,会发生什么?
所有闭包会捕获同一个变量,循环结束后调用函数时您将获得相同的值。
示例代码:
func main() { a := []func(){} for i := 0; i < 3; i++ { a = append(a, func() { fmt.Println(i) }) } for _, f := range a { f() } // 3 3 3 }
为避免这种情况,请在循环体内创建一个新变量:
for i := 0; i < 3; i++ { j := i a = append(a, func() { fmt.Println(j) }) // 0 1 2 }
可以将匿名函数作为interface{}类型的值使用吗?
函数仅与interface{}兼容,而不与其他接口兼容,不能相互比较(除了nil)。如果将闭包传递为interface{},只能通过类型转换调用其func签名。
匿名函数可以递归调用吗?
可以,但前提是首先声明一个闭包的变量名称,然后再将函数赋值给它。
示例代码:
var fib func(n int) int fib = func(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(10)) // 55
在遍历回调列表的循环中,开发者将处理程序绑定为闭包,捕获迭代器变量。所有回调都使用不正确的值,导致错误。
优点:
缺点:
在循环内部为每个闭包创建新变量,保证正确捕获值和预期行为。
优点:
缺点: