概念
- 回调函数:是指函数作为参数传递到另一个函数中,然后在另一个函数中被调用。可以接受一个或多个函数作为输入。
- 匿名函数:是没有函数名的函数。由一个不带函数名的函数声明和函数体组成。匿名函数可以赋值给一个变量或者直接执行。
- 闭包:当匿名函数引用了外部作用域中的变量时就成了闭包。使用闭包的意义主要就是缩小变量作用域,减少对全局变量的污染。
回调函数
下面是几个示例:
加法运算
package main
import "fmt"
func Add(a, b int) {
fmt.Printf("%d + %d = %d\n", a, b, a+b)
}
// 接受一个整数 y 和一个函数 f 作为参数
func callback(y int, f func(int, int)) {
// 在函数内部,调用函数 f,并将 y 和整数值 2 作为参数传递给 f
f(y, 2)
}
// sumNum 接受一个整数切片 data 和一个回调函数 call 作为参数
func sumNum(data []int, call func(int)) {
sum := 0
for _, v := range data {
sum += v
}
// 将总和作为参数传递给回调函数 call
call(sum)
}
func main() {
// 将 1 和函数 Add 作为参数,Add(1, 2)
callback(1, Add)
sumNum([]int{1, 2, 3}, func(num int) {
fmt.Println("sum:", num)
})
sumNum([]int{1, 2, 3}, func(num int) {
if num < 10 {
fmt.Println("sum < 10")
} else {
fmt.Println("sum > 10")
}
})
}
事件处理
package main
import (
"fmt"
"time"
)
type Button struct {
// ClickedHandler 用于保存按钮被点击时要执行的函数
ClickedHandler func()
}
// Click 该方法接受一个函数作为参数,并将其赋值给 ClickedHandler 字段
func (b *Button) Click(handler func()) {
b.ClickedHandler = handler
}
func main() {
button := &Button{}
// 传递一个匿名函数作为参数,匿名函数定义了按钮被点击时要执行的逻辑
button.Click(func() {
fmt.Println("按钮被点击了")
})
// 模拟等待用户点击按钮的时间
time.Sleep(2 * time.Second)
// 检查 button.ClickedHandler 字段是否为 nil,来判断是否有回调函数注册
if button.ClickedHandler != nil {
// 如果有回调函数注册,调用 button.ClickedHandler() 来触发回调函数的执行
button.ClickedHandler()
}
}
sort排序
func Slice(x any, less func(i int, j int) bool)
func SliceStable(x any, less func(i int, j int) bool)
// Slice/SliceStable使用less函数对x进行排序。x必须是切片。
package main
import (
"fmt"
"sort"
)
func main() {
numbers := []int{3, 5, 2, 1, 4}
sort.SliceStable(numbers, func(i, j int) bool {
return numbers[i] < numbers[j]
})
fmt.Println(numbers)
}
[1 2 3 4 5]
异步处理
package main
import (
"fmt"
"io"
"net/http"
)
// getData 函数接受一个URL和一个回调函数作为参数
func getData(url string, callback func(data []byte, err error)) {
// 异步发起http请求
go func() {
res, err := http.Get(url)
if err != nil {
callback(nil, err)
return
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
callback(nil, err)
return
}
callback(data, nil)
}()
}
func main() {
url := "http://www.baidu.com"
getData(url, func(data []byte, err error) {
if err != nil {
fmt.Println("数据获取失败:", err)
return
}
fmt.Println("数据获取成功:", string(data))
})
}
匿名函数
使用情况分为三种:赋值给一个变量,直接执行,作为参数即上面的回调函数。
赋值给一个变量
package main
import "fmt"
func main() {
sumFun := func(x, y int) int {
return x + y
}
sum := sumFun(1, 2)
fmt.Println(sum)
f := func() {
fmt.Println("anonymous function")
}
f()
fmt.Println(f)
fmt.Printf("%T\n", f)
}
直接执行
package main
import "fmt"
func main() {
func() {
fmt.Println("Hello World!")
}()
func(str string) {
fmt.Println("Hello " + str)
}("World!")
}
闭包
闭包引用了函数体之外的变量,这个变量叫自由变量。匿名函数可以对这个引用的变量进行访问和赋值,即使这些变量已经超出了其作用域的生命周期。
Golang中所有的匿名函数都是闭包。
实现:在函数内部定义局部变量,把另一个函数当作返回值,局部变量对于匿名函数就相当于全局变量,所以多次调用匿名函数,局部变量的值也跟随变化。
需要注意的是,闭包可能会导致内存泄漏问题。因为闭包会持有对外部变量的引用,如果闭包的生命周期比外部变量的生命周期长,那么这些外部变量将无法被垃圾回收,从而导致内存泄漏。
计数器
package main
import "fmt"
// Count 函数不接受任何参数,但返回一个函数。
func Count() func(int) int {
a := 0
// 返回一个匿名函数
return func(b int) int {
a += b
return a
}
}
func main() {
// 调用 Count 函数并将返回的函数赋值给变量 count
count := Count()
fmt.Println(count(1)) // 1
fmt.Println(count(1)) // 2
// 重新调用 Count() 会重新声明及赋值局部变量 a
count2 := Count()
fmt.Println(count(1)) // 1
}
值传递
package main
import "fmt"
func main() {
i := 0
// 闭包
defer func() {
fmt.Println("闭包:", i)
}()
// 非闭包
defer fmt.Println("非闭包:", i)
i = 10
}
非闭包: 0
闭包: 10