Go闭包与常用场景

在很多的开源项目里,经常看到闭包的运用,在 Go语言中,函数类型是一种特殊的类型,函数类型可以像其他类型一样被声明、赋值给变量、作为参数传递。进而有了匿名函数、闭包。本文将简要记录闭包的概念和一些常用的场景。

什么是闭包

Go函数可以是闭包。闭包是一个函数值,它从函数体外部引用变量。函数可以访问被引用的变量并对其赋值;从这个意义上说,函数被"绑定"到变量上。-- a Tour of Go

一个最简单的例子: 1~10的加法

go 复制代码
package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
       sum += x
       return sum
    }
}

func main() {
    pos := adder()
    for i := 0; i < 10; i++ {
       fmt.Println(
          pos(i),
       )
    }
}

//OUTPUT
//0
//1
//3
//6
//10
//15
//21
//28
//36
//45

上例中,adder函数返回一个闭包。在闭包内部,它引用了外部的变量sum

闭包和匿名函数的区别

匿名函数是指在代码中没有明确命名的函数,闭包是一个函数值,它可以访问其词法环境中捕获的变量。匿名函数可以是闭包,也可以不是闭包。匿名函数引用了外部作用域中的变量,它就成为闭包。

go 复制代码
func main() {
	x := 10

	// 匿名函数,不是闭包
	anonymousFunc := func() {
		fmt.Println("匿名函数:", x)
	}
	anonymousFunc()

	// 闭包
	closureFunc := func() {
		fmt.Println("闭包:", x)
	}
	closureFunc()
}

常见的应用场景

闭包在实际编程中具有广泛的应用。以下是几个常见的应用场景:

  1. 保存状态: 通过捕获外部变量,闭包可以在函数调用之间保留状态信息,如迭代器
  2. 函数工厂: 根据不同的配置参数来动态创建函数,
  3. 回调函数:将一个函数作为参数传递给另一个函数,通过闭包,捕获一些上下文信息并执行该函数
  4. 并发编程:可以安全地在多个goroutine中共享和修改变量,一种简洁的方式

保存状态 -- 迭代器

go 复制代码
package main

import "fmt"

func Iterator(values []int) func() (int, bool) {
    index := 0

    return func() (int, bool) {
       if index >= len(values) {
          return 0, false
       }

       value := values[index]
       index++

       return value, true
    }
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    iterate := Iterator(numbers)

    for {
       value, ok := iterate()
       if !ok {
          break
       }

       fmt.Println(value)
    }
} 

//OUTPUT:
//1
//2
//3
//4
//5 

上例中,index为闭包的共享状态。Iterator创建一个闭包函数,这个闭包函数用于迭代切片中的元素。其中,闭包每次执行,index会递增,直到循环结束。比如在很多开源框架的中间件中,会使用迭代器模式实现。

函数工厂

go 复制代码
package main

import "fmt"

func FunctionFactory(operation string) func(int, int) int {
    switch operation {
    case "add":
       return func(a, b int) int {
          return a + b
       }
    case "subtract":
       return func(a, b int) int {
          return a - b
       }
    case "multiply":
       return func(a, b int) int {
          return a * b
       }
    case "divide":
       return func(a, b int) int {
          if b != 0 {
             return a / b
          }
          return 0
       }
    default:
       return nil
    }
}

func main() {
    addFunc := FunctionFactory("add")
    subtractFunc := FunctionFactory("subtract")
    multiplyFunc := FunctionFactory("multiply")
    divideFunc := FunctionFactory("divide")

    fmt.Println(addFunc(5, 3))       
    fmt.Println(subtractFunc(10, 2)) 
    fmt.Println(multiplyFunc(4, 5))  
    fmt.Println(divideFunc(10, 2))   
    fmt.Println(divideFunc(10, 0))   
}
//OUTPUT:
8
8 
20
5 
0 

上例中,FunctionFactory分别提供加法、减法、乘法和除法的闭包函数,并封装为函数工厂,使得函数的创建更加灵活和可定制。

回调函数

go 复制代码
package main

import (
    "fmt"
    "time"
)

func PerformOperationAsync(input int, callback func(int)) {
    go func() {
       time.Sleep(2 * time.Second)
       result := input * 2
       callback(result)
    }()
}

func main() {
    callback := func(result int) {
       fmt.Println("操作结果:", result)
    }
    PerformOperationAsync(5, callback)
    time.Sleep(3 * time.Second)
}

//OUTPUT
//操作结果:10

上例中,使用匿名函数创建了一个闭包callback,即回调函数。在执行异步操作时,它会将计算结果result传递给回调函数。比如,我们需要等待某个长时间的操作或者某个事件触发之后的场景。

并发编程

go 复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

func ConcurrentTask(tasks []func() int) []int {
    results := make([]int, len(tasks))
    wg := sync.WaitGroup{}

    for i, task := range tasks {
       wg.Add(1)
       go func(i int, task func() int) {
          defer wg.Done()
          results[i] = task()
       }(i, task)
    }

    wg.Wait()

    return results
}

func main() {
    tasks := []func() int{
       func() int {
          time.Sleep(2 * time.Second)
          return 1
       },
       func() int {
          time.Sleep(1 * time.Second)
          return 2
       },
       func() int {
          time.Sleep(3 * time.Second)
          return 3
       },
    }

    results := ConcurrentTask(tasks)
    fmt.Println(results) // 输出:[1 2 3]
} 

//OUTPUT
//[1 2 3]

上例中,for循环为每个任务创建一个匿名函数。这些匿名函数使用闭包来捕获循环变量i和任务函数task。在每个匿名函数内部,我们调用任务函数,并将结果存储在相应的位置。

总结

本文主要学习记录Go语言中一个强大、极具表现力的特性:闭包。包括闭包的基础概念,和匿名函数的区别,以及一些常见的编程场景。闭包因为其灵活性,可塑性强,在开源库里广泛运用,对提升代码的可维护性、拓展性上有比较大的帮助。

参考

相关推荐
研究司马懿17 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘2 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤2 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto4 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo