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

参考

相关推荐
Serverless社区2 小时前
一键部署QwQ-32B推理模型,2种方式简单、快速体验
go
大鹏dapeng2 小时前
Gone V2 Provider 机制介绍
后端·go·github
Nathan__275 小时前
go-文件缓存与锁
缓存·go·
一个热爱生活的普通人5 小时前
如何用golang实现一个MCP Server
llm·go·mcp
江湖十年8 小时前
如何基于 Go 语言设计一个简洁优雅的分布式任务系统
分布式·后端·go
upsilon1 天前
golang切片slice
后端·go
upsilon1 天前
golang数组
后端·go
aricvvang1 天前
Go结构体优化(对齐方式)
后端·go
湫qiu1 天前
手写 Protobuf —— Java 代码生成
java·go·protobuf
侯大宝1 天前
开箱即用的go-zero示例
go·go-zero