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

参考

相关推荐
BlockChain88810 小时前
Solidity 实战【二】:手写一个「链上资金托管合约」
go·区块链
BlockChain88818 小时前
Solidity 实战【三】:重入攻击与防御(从 0 到 1 看懂 DAO 事件)
go·区块链
剩下了什么1 天前
Gf命令行工具下载
go
地球没有花1 天前
tw引发的对redis的深入了解
数据库·redis·缓存·go
BlockChain8881 天前
字符串最后一个单词的长度
算法·go
龙井茶Sky1 天前
通过higress AI统计插件学gjson表达式的分享
go·gjson·higress插件
宇宙帅猴3 天前
【Ubuntu踩坑及解决方案(一)】
linux·运维·ubuntu·go
SomeBottle3 天前
【小记】解决校园网中不同单播互通子网间 LocalSend 的发现问题
计算机网络·go·网络编程·学习笔记·计算机基础
且去填词4 天前
深入理解 GMP 模型:Go 高并发的基石
开发语言·后端·学习·算法·面试·golang·go
大厂技术总监下海4 天前
向量数据库“卷”向何方?从Milvus看“全功能、企业级”的未来
数据库·分布式·go·milvus·增强现实