闭包在Go语言中是一个能够访问并操作其外部作用域变量的函数,即使外部函数已经执行完毕。闭包由函数体和其引用的环境(外部变量)组成,及:闭包 = 函数 + 环境。

闭包的特性:

  1. 捕获外部变量:内部函数可访问外部作用域的变量
  2. 状态保持:捕获的变量生命周期延长
  3. 隔离性:每次调用外部函数会创建新的闭包实例

示例说明

示例1:

package mainimport ("fmt"
)func Exp(n int) func() int {e := 1 // 闭包环境变量,在多次调用中保持状态return func() int {temp := e    // 1. 保存当前值(闭包捕获时的值)e *= n       // 2. 更新环境变量(n来自外部函数参数)return temp  // 3. 返回保存的旧值}
}func main() {grow := Exp(2) // 创建闭包实例,此时:// - n 被固定为2// - e 初始化为1,与闭包绑定// 每次调用grow()时的状态变化:// 调用1: temp=1 → e=2 → return 1 (2^0)// 调用2: temp=2 → e=4 → return 2 (2^1)// 调用3: temp=4 → e=8 → return 4 (2^2)// ...for i := range 10 {fmt.Printf("2^%d=%d\n", i, grow())}
}

输出:

2^0=1
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128
2^8=256
2^9=512

内存状态演变示意图:

调用次数 | e值  | 返回值 | 对应指数
---------------------------------0    | 1    |   -    | 初始状态1    | 2    |   1    | 2^02    | 4    |   2    | 2^1 3    | 8    |   4    | 2^2...10   | 1024 | 512    | 2^9

 Exp函数的返回值是一个函数,这里将称成为grow函数,每将它调用一次,变量e就会以指数级增长一次。grow函数引用了Exp函数的两个变量:en,它们诞生在Exp函数的作用域内,在正常情况下随着Exp函数的调用结束,这些变量的内存会随着出栈而被回收。但是由于grow函数引用了它们,所以它们无法被回收,而是逃逸到了堆上,即使Exp函数的生命周期已经结束了,但变量en的生命周期并没有结束,在grow函数内还能直接修改这两个变量,grow函数就是一个闭包函数。

示例2:带参数的闭包(函数工厂)

func multiplier(factor int) func(int) int {return func(x int) int {return x * factor // 捕获外部参数}
}func main() {double := multiplier(2)triple := multiplier(3)fmt.Println(double(5))  // 10 (2*5)fmt.Println(triple(5))  // 15 (3*5)// 修改外部变量factor := 4specialMulti := func(x int) int { return x * factor }factor = 5 // 闭包使用最新的值fmt.Println(specialMulti(10)) // 50 (5*10)
}

具体执行流程:

  1. multiplier(2) 返回闭包时,捕获了 factor=2
  2. 当调用 double(5) 时:
    • 闭包接收参数 x=2
    • 执行计算 5 * 2(factor的值)
    • 返回结果10

后面的修改外部变量关键作用点:

  1. 延迟绑定:闭包在调用时(而非定义时)获取变量的当前值
  2. 动态更新:展示闭包捕获的变量可以响应外部修改
  3. 引用捕获:验证Go的闭包捕获的是变量引用,而非值拷贝

内存状态变化示意图:

初始状态:
factor = 4
specialMulti闭包 → 指向factor的内存地址修改后:
factor = 5
specialMulti闭包 → 仍然指向同一个内存地址调用时计算:
10 * 5 = 50

示例3:状态隔离(并发安全)

func main() {var wg sync.WaitGroup  // 1. 创建等待组for i := 0; i < 3; i++ {wg.Add(1)  // 2. 每次循环增加计数器go func(id int) {  // 3. 启动goroutinedefer wg.Done()  // 5. 任务完成时减少计数器fmt.Printf("Goroutine %d\n", id)}(i)  // 4. 传递当前i的副本}wg.Wait()  // 6. 阻塞直到计数器归零
}

输出(可能顺序不同):

Goroutine 0
Goroutine 1
Goroutine 2

关键机制说明

1.WaitGroup 三部曲

  • Add(1):在启动每个goroutine前增加计数器
  • Done():在每个goroutine完成时减少计数器(通过defer确保执行)
  • Wait():主goroutine阻塞等待所有任务完成

2.闭包参数传递

go func(id int) { ... }(i)  // 传递i的当前值副本

 3.并发执行流程

主goroutine        Goroutine 0      Goroutine 1      Goroutine 2
│─启动循环─────────┐
│ wg.Add(1)       ├─→ 执行任务
│ 启动goroutine 0─┤
│ wg.Add(1)       ├─────────────→ 执行任务
│ 启动goroutine 1─┤
│ wg.Add(1)       ├───────────────────────────→ 执行任务
│ 启动goroutine 2─┤
│ wg.Wait()───────┼─────────────────────────────────────┤
│                 │←───────────────────── Done() ───────┘

常见陷阱与解决方案

陷阱:循环变量捕获

func main() {var funcs []func()for i := 0; i < 3; i++ {funcs = append(funcs, func() {fmt.Println(i) // 总是输出3!})}for _, f := range funcs {f()}
}

输出:

3
3
3

原因: 所有闭包共享同一个i的引用

解决方案1:参数传递

for i := 0; i < 3; i++ {j := i // 创建新变量funcs = append(funcs, func() {fmt.Println(j) // 正确输出0,1,2})
}

解决方案2:立即执行

for i := 0; i < 3; i++ {func(i int) { // 参数隔离funcs = append(funcs, func() {fmt.Println(i)})}(i)
}

闭包的实际应用场景

  1. 状态封装:私有计数器/计时器
  2. 中间件:Web请求处理链
       func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {log.Println(r.URL.Path)next.ServeHTTP(w, r)})}
  3. 资源管理:文件操作自动关闭
       func readFile(name string) func() ([]byte, error) {f, err := os.Open(name)return func() ([]byte, error) {defer f.Close() // 捕获文件句柄return io.ReadAll(f)}}
  4. 函数柯里化:参数分解
       func add(a int) func(int) int {return func(b int) int {return a + b}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/94477.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94477.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/94477.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【DL学习笔记】Dataset类功能以及自定义

文章目录一、Dataset 与 DataLoader 功能介绍抽象类Dataset的作用DataLoader 作用两者关系二、自定义Dataset类Dataset的三个重要方法__len__()方法_getitem__()方法__init__ 方法三、现成的torchvision.datasets模块MNIST举例COCODetection举例torchvision.datasets.MNIST使用…

Python爬虫实战:研究python_reference库,构建技术研究数据系统

1. 引言 1.1 研究背景与意义 在大数据时代,数据已成为重要的生产要素。互联网作为全球最大的信息库,蕴含着海量有价值的数据。如何从纷繁复杂的网络信息中快速、准确地提取所需数据,成为各行各业面临的重要课题。网络爬虫技术作为数据获取的关键手段,能够模拟人类浏览网页…

Web开发系列-第15章 项目部署-Docker

第15章 项目部署-Docker Docker技术能够避免部署对服务器环境的依赖&#xff0c;减少复杂的部署流程。 轻松部署各种常见软件、Java项目 参考文档&#xff1a;‌&#xfeff;‬‌&#xfeff;‍‍‌‍⁠⁠&#xfeff;‍‍&#xfeff;‬‌‍‌‬⁠‌‬第十五章&#xff1a;…

微软无界鼠标(Mouse without Borders)安装及使用:多台电脑共用鼠标键盘

文章目录一、写在前面二、下载安装1、两台电脑都下载安装2、被控端3、控制端主机三、使用一、写在前面 在办公中&#xff0c;我们经常会遇到这种场景&#xff0c;自己带着笔记本电脑外加公司配置的台式机。由于两台电脑&#xff0c;所以就需要搭配两套键盘鼠标。对于有限的办公…

nodejs 编程基础01-NPM包管理

1:npm 包管理介绍 npm 是nodejs 的包管理工具&#xff0c;类似于java 的maven 和 gradle 等&#xff0c;用来解决nodejs 的依赖包问题 使用场景&#xff1a;1. 从NPM 服务骑上下载或拉去别人编写好的第三方包到本地进行使用2. 将自己编写代码或软件包发布到npm 服务器供他人使用…

基于Mediapipe_Unity_Plugin实现手势识别

GitHub - homuler/MediaPipeUnityPlugin: Unity plugin to run MediaPipehttps://github.com/homuler/MediaPipeUnityPlugin 实现了以下&#xff1a; public enum HandGesture { None, Stop, ThumbsUp, Victory, OK, OpenHand } 核心脚本&#xff1a…

Android 项目构建编译概述

主要内容是Android AOSP源码的管理方式&#xff0c;项目源码的构建和编译&#xff0c;用到比如git、repo、gerrit一些命令工具&#xff0c;以及使用Soong编译系统&#xff0c;编写Android.bp文件的格式样式。 1. Android操作系统堆栈概述 Android 是一个针对多种不同设备类型打…

Python爬虫08_Requests聚焦批量爬取图片

一、Requests聚焦批量爬取图片 import re import requests import os import timeurl https://www.douban.com/ userAgent {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0}#获取整个浏览页面 page_text requests.get(urlur…

Spring Cloud系列—简介

目录 1 单体架构 2 集群与分布式 3 微服务架构 4 Spring Cloud 5 Spring Cloud环境和工程搭建 5.1 服务拆分 5.2 示例 5.2.1 数据库配置 5.2.2 父子项目创建 5.2.3 order_service子项目结构配置 5.2.4 product_service子项目结构配置 5.2.5 服务之间的远程调用 5.…

【普中STM32精灵开发攻略】--第 1 章 如何使用本攻略

学习本开发攻略主要参考的文档有《STM32F1xx 中文参考手册》和《Cortex M3权威指南(中文)》&#xff0c;这两本都是 ST 官方手册&#xff0c;尤其是《STM32F1xx 中文参考手册》&#xff0c;里面包含了 STM32F1 内部所有外设介绍&#xff0c;非常详细。大家在学习 STM32F103的时…

【Docker】RK3576-Debian上使用Docker安装Ubuntu22.04+ROS2

1、简述 RK3576自带Debian12系统,如果要使用ROS2,可以在Debian上直接安装ROS2,缺点是有的ROS包需要源码编译;当然最好是使用Ubuntu系统,可以使用Docker安装,或者构建Ubuntu系统,替换Debian系统。 推荐使用Docker来安装Ubuntu22.04,这里会有个疑问,是否可以直接使用Do…

解决docker load加载tar镜像报json no such file or directory的错误

在使用docker加载离线镜像文件时&#xff0c;出现了json no such file or directory的错误&#xff0c;刚开始以为是压缩包拷贝坏了&#xff0c;重新拷贝了以后还是出现了问题。经过网上查找方案&#xff0c;并且自己实践&#xff0c;采用下面的简单方法就可以搞定。 归结为一句…

《协作画布的深层架构:React与TypeScript构建多人实时绘图应用的核心逻辑》

多人在线协作绘图应用的构建不仅是技术栈的简单组合,更是对实时性、一致性与用户体验的多维挑战。基于React与TypeScript开发这类应用,需要在图形绘制的基础功能之外,解决多用户并发操作的同步难题、状态回溯的逻辑冲突以及大规模协作的性能瓶颈。每一层架构的设计,都需兼顾…

智慧社区(八)——社区人脸识别出入管理系统设计与实现

在社区安全管理日益智能化的背景下&#xff0c;传统的人工登记方式已难以满足高效、精准的管理需求。本文将详细介绍一套基于人脸识别技术的社区出入管理系统&#xff0c;该系统通过整合腾讯云 AI 接口、数据库设计与业务逻辑&#xff0c;实现了居民出入自动识别、记录追踪与访…

嵌入式开发学习———Linux环境下IO进程线程学习(四)

进程相关函数fork创建一个子进程&#xff0c;子进程复制父进程的地址空间。父进程返回子进程PID&#xff0c;子进程返回0。pid_t pid fork(); if (pid 0) { /* 子进程代码 */ } else { /* 父进程代码 */ }getpid获取当前进程的PID。pid_t pid getpid();getppid获取父进程的P…

标记-清除算法中的可达性判定与Chrome DevTools内存分析实践

引言 在现代前端开发中&#xff0c;内存管理是保证应用性能与用户体验的核心技术之一。作为JavaScript运行时的基础机制&#xff0c;标记-清除算法(Mark-and-Sweep) 通过可达性判定决定哪些内存需要回收&#xff0c;而Chrome DevTools提供的Memory工具则为开发者提供了深度的内…

微算法科技(NASDAQ:MLGO)基于量子重加密技术构建区块链数据共享解决方案

随着信息技术的飞速发展&#xff0c;数据已成为数字经济时代的核心生产要素。数据的共享和安全往往是一对难以调和的矛盾。传统的加密方法在面对日益强大的计算能力和复杂的网络攻击时&#xff0c;安全性受到了挑战。微算法科技(NASDAQ&#xff1a;MLGO)通过引入量子重加密技术…

FastAPI快速入门P2:与SpringBoot比较

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录引言1 FastAPI事件管理2 类的使用2.1 初始化方法对…

SAP-ABAP: Open SQL集合函数COUNT(统计行数)、SUM(数值求和)、AVG(平均值)、MAX/MIN(极值)深度指南

SAP Open SQL集合函数深度指南 1. 核心价值与特性函数作用关键特性COUNT统计行数用COUNT(*)包含NULL值行&#xff0c;COUNT(字段)排除NULLSUM数值求和自动过滤NULL值&#xff0c;结果类型与源字段相同AVG平均值必须用TYPE f接收&#xff0c;否则四舍五入导致精度丢失MAX/MIN极值…

【docker】UnionFS联合操作系统

Linux 的 Namespace、CGroups 和 UnionFS 三大技术支撑了 Docker 的实现。 一、为什么需要联合文件系统&#xff1f;在传统操作系统中&#xff0c;每个文件系统都是独立的孤岛。但当我们需要&#xff1a;合并多个目录的内容保持基础系统不变的同时进行修改高效共享重复文件内容…