1. Go 的第一类对象(First-Class Citizens)

什么是第一类对象?

  • 第一类对象是指能够像 普通值 一样使用的对象,通常可以赋值给变量、传递给函数、作为函数返回值等。
  • 在很多编程语言中,函数本身不被视为第一类对象(例如 C),它们是通过函数指针或类似机制来操作而在 Go 中,函数被视为 第一类对象,意味着函数可以像其他数据类型一样被处理

Go 中的第一类对象:

Go 语言将 函数 作为第一类对象,这使得它们可以:

  1. 作为 变量 被赋值和传递。
  2. 作为 参数 被传递给其他函数。
  3. 作为 返回值 从函数返回。
  4. 与其他数据类型(如 intstringstruct 等)一样操作。

示例:函数作为第一类对象

package mainimport "fmt"// 定义一个简单的函数
func add(a, b int) int {return a + b
}func main() {// 将函数赋值给变量var f func(int, int) intf = add  // 函数赋值给变量 f// 通过变量调用函数result := f(2, 3)fmt.Println("Result:", result)  // 输出:Result: 5
}
  • 你可以将函数 add 赋值给变量 f,并通过变量 f 来调用 add 函数。
  • 函数 add 本质上是一个值,存储在变量 f 中,f 是一个 函数类型的变量

2. 闭包(Closure)

什么是闭包?

闭包是一个函数,它不仅包含了函数的 代码,还 捕获保留 外部作用域中的变量。闭包让函数可以访问其外部函数的变量,即使外部函数已经返回,闭包仍然能够使用这些变量。

在 Go 中,闭包是一种非常强大的概念,允许函数在其外部环境中“记住”并 操作 捕获的变量。闭包使得 Go 支持许多 函数式编程 的特性,如高阶函数、回调函数等。

闭包的关键特性

  1. 捕获外部变量:闭包能够捕获并访问定义它的函数外部的变量。
  2. 函数和数据绑定:闭包会把外部变量和函数绑定在一起,即使外部函数已经返回,闭包依然能访问这些变量。
  3. 状态保持:闭包允许函数保持对外部变量的引用,从而让它们保持一个状态。

闭包的创建

在 Go 中,闭包是通过 函数返回值 来创建的,返回的函数可以访问外部函数的局部变量。

示例:闭包的基本使用

package mainimport "fmt"// 返回一个闭包
func makeCounter() func() int {count := 0return func() int {count++return count}
}func main() {// 创建闭包counter := makeCounter()// 每次调用闭包时,count 都会增加fmt.Println(counter()) // 输出:1fmt.Println(counter()) // 输出:2fmt.Println(counter()) // 输出:3
}

解释

  • makeCounter 函数返回一个闭包,这个闭包引用了 count 变量。
  • counter 是一个闭包,每次调用它时,它都会增加 count 并返回新的值。
  • count 变量是 捕获的外部变量,即使 makeCounter 函数已经返回,闭包仍然能够访问和修改 count

3. 闭包的详细工作原理

捕获变量

  • 当 Go 创建一个闭包时,闭包会 捕获 外部函数的变量,保留它们的引用,而不是拷贝它们的值。这使得闭包能够保留对这些变量的访问权,直到闭包不再使用这些变量为止。

生命周期和内存管理

  • Go 的垃圾回收机制会确保闭包的内存得到正确管理。如果闭包捕获了某些变量,这些变量不会在闭包生命周期结束时被回收,直到闭包本身不再被引用。
  • 这使得闭包在需要持有外部状态(如计数器、缓存等)时非常有用。
示例:闭包和外部变量的作用域
package mainimport "fmt"func main() {var counter int// 创建闭包,闭包引用外部变量 counterincrement := func() int {counter++return counter}// 调用闭包fmt.Println(increment()) // 输出:1fmt.Println(increment()) // 输出:2fmt.Println(increment()) // 输出:3
}

解释

  • 闭包 increment 每次调用时都会访问并修改外部的 counter 变量,闭包保留了对外部变量 counter 的引用,每次调用时都增加 counter 的值。

4. 闭包和 Go 的内存管理

Go 的垃圾回收机制会确保闭包中的变量在不再使用时被正确清理。例如,在上面的 makeCounter 例子中,闭包 counter 持有对 count 变量的引用。只要 counter 被引用,count 就不会被垃圾回收。只有在 counter 不再被引用时,闭包才会释放相关的内存。


5. 闭包的常见应用场景

  1. 回调函数和异步操作

    • 闭包在回调函数中广泛使用,可以保持外部变量的状态,尤其在异步操作和事件驱动编程中非常有用。
  2. 函数工厂

    • 闭包可用作 工厂函数,生成具有不同行为的函数。
  3. 状态保持

    • 闭包非常适合实现需要持久状态的逻辑,如 计数器缓存 等。
  4. 函数式编程模式

    • 闭包是实现 函数式编程(如高阶函数)的基础,允许函数返回另一个函数,或者使用函数作为参数

6.区分闭包与普通函数

在 Go 中,闭包(Closure)普通函数 之间的区别主要体现在它们是否捕获外部变量的值。普通函数 没有 捕获外部变量,而闭包 会捕获外部函数的局部变量

1. 闭包与普通函数的本质区别:

  • 普通函数:一个普通的函数,它的行为是固定的,不依赖于外部的变量或上下文。普通函数 没有 捕获外部变量的能力。
  • 闭包:一个函数,它捕获并“记住”外部函数的变量,即使外部函数的作用域已经结束。闭包会持有对外部变量的引用,并且可以在函数外部继续访问这些变量。

2 实例区分:

普通函数
package mainimport "fmt"// 普通函数:不依赖外部变量,只根据输入参数工作
func add(a, b int) int {return a + b
}func main() {fmt.Println(add(2, 3)) // 输出 5
}

解释:

  • 这个 add 函数是一个普通函数,它只根据输入的 ab 进行计算,不依赖于任何外部的变量。
  • 它的行为 完全由输入参数决定,不依赖于外部的状态。
闭包
package mainimport "fmt"// 闭包:函数内部访问并捕获外部变量
func makeMultiplier(factor int) func(int) int {return func(x int) int {return x * factor // 使用外部捕获的变量 `factor`}
}func main() {multiplyBy2 := makeMultiplier(2)  // 创建闭包,factor = 2multiplyBy3 := makeMultiplier(3)  // 创建闭包,factor = 3fmt.Println(multiplyBy2(5))  // 输出 10fmt.Println(multiplyBy3(5))  // 输出 15
}

解释:

  • 这个 makeMultiplier 函数返回了一个闭包。这个闭包引用了外部变量 factor,并根据 factor 执行不同的计算。即使 makeMultiplier 函数已经返回,闭包仍然能够 记住 factor 的值,并在后续的调用中使用它。
  • 这里的 multiplyBy2multiplyBy3 是两个闭包,它们分别捕获了 factor 的值 23

3. 如何通过代码结构判断:

  • 普通函数 通常是直接定义在包内或者文件中的独立函数,且它们的参数和返回值类型是固定的,不依赖外部的变量。
  • 闭包 通常是由 内部函数 返回的,外部函数的局部变量在闭包中被捕获并且可以继续访问。
示例:闭包与普通函数的结构对比
package mainimport "fmt"// 普通函数
func square(x int) int {return x * x
}// 闭包函数:捕获并使用外部变量
func createAdder(y int) func(int) int {return func(x int) int {return x + y // 捕获并使用外部变量 y}
}func main() {// 普通函数调用fmt.Println(square(4)) // 输出 16// 闭包函数调用add5 := createAdder(5)  // 返回一个闭包fmt.Println(add5(10))    // 输出 15,闭包捕获了 y = 5
}

区别

  • square 是一个普通函数,它 不依赖外部变量,它只使用它的参数 x 来计算。
  • createAdder 返回一个闭包,闭包 捕获并使用了外部函数的局部变量 y。每次调用 add5 都是通过闭包引用了 y = 5 这个值。

6. 注意点

  • 闭包捕获的是变量的引用,而不是它的值。例如,如果一个闭包捕获了一个变量,并且该变量在外部函数中发生了改变,闭包将访问到变量的最新值。
package mainimport "fmt"func main() {x := 10increment := func() int {x++return x}fmt.Println(increment()) // 输出:11fmt.Println(increment()) // 输出:12
}

解释:

  • 闭包 increment 捕获了外部变量 x,并且每次调用闭包时,x 的值都会递增。
  • x 不是在闭包创建时固定的值,而是 被引用,因此闭包可以改变它的值。

7. 总结

  • 普通函数:不依赖外部作用域的变量。它的输入和输出是完全由它的参数决定的,不会修改外部状态。
  • 闭包:定义在 外部函数内部,并且 捕获并持有外部函数的局部变量,即使外部函数执行完毕,闭包依然能够访问这些变量。

通过这些规则和结构,你可以轻松区分一个函数是闭包还是普通函数。如果你有更多问题或需要进一步的解释,请告诉我!

7. 总结:Go 中的第一类对象与闭包

  • 第一类对象:Go 中的函数是第一类对象,它们可以赋值给变量、作为参数传递、作为返回值等,这使得 Go 的函数非常灵活。
  • 闭包:Go 的闭包是捕获并保留外部作用域变量的函数。闭包可以访问其定义时外部函数的局部变量,即使外部函数已经返回。闭包允许你保持状态,并提供强大的功能,尤其在需要函数式编程的场景中。

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

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

相关文章

深度分析Android多线程编程

理解并正确运用多线程是构建高性能、流畅、响应迅速的 Android 应用的关键,但也充满挑战和陷阱。 核心挑战:UI 线程(主线程)的限制 唯一性: Android 应用只有一个主线程,负责处理所有用户交互(触…

uniapp在app中关于解决输入框键盘弹出后遮住输入框问题

问题描述: uniapp的app中,当表单页面过长时,点击下方的输入框时,弹出键盘后会把输入框给挡住,导致看不到输入内容。 解决方案: 在page.json中,找到此页面的配置,加上style中的softin…

二分查找----5.寻找旋转排序数组中的最小值

题目链接 /** 数组在某处进行旋转,分割为两个独立的递增区间,找出数组的最小值;特殊情况:若旋转次数是数组长度的倍数,则数组不变 特点: 常规情况: 数组被分割为两个独立的子区间,左半区的最小值大于右半区的最大值 依据数组长度,mid可能落在左半区也有可能落在右半区,最小值在…

Eureka-服务注册,服务发现

在远程调用的时候&#xff0c;我们写的url是写死的。 String url "<http://127.0.0.1:9090/product/>" orderInfo.getProductId();当换个机器&#xff0c;或者新增个机器&#xff0c;导致ip变换&#xff0c;从而使得 url 发生了变化&#xff0c;接着就需要去…

ubuntu24的一些小问题

截图Keyboard -> Keyboard Shortcus -> View and customize Shortcus如上&#xff0c;可以修改默认的快捷按键。比如截图按键可以修改。 ibus输入法无法&#xff0c;输入V异常问题 也是困扰了很久&#xff0c;发现是这样的&#xff1a;https://github.com/libpinyin/ibus…

Python Locust库详解:从入门到分布式压力测试实战

一、Locust核心优势 作为一款基于Python的开源负载测试工具&#xff0c;Locust通过协程架构实现了高效资源利用。其独特优势体现在&#xff1a; 纯Python脚本&#xff1a;用熟悉的语言定义用户行为&#xff0c;支持条件判断和复杂逻辑分布式扩展&#xff1a;单节点支持数千并发…

Redis数据类型与内部编码

在Redis中通常普遍认为&#xff0c;使用redis的能进行查询&#xff0c;插入&#xff0c;删除&#xff0c;修改操作都是O(1)是因为他是利用hash表实现的&#xff0c;但是&#xff0c;背后的实现不一定是一个标准的hash表&#xff0c;它内部的数据类型还会有变数&#xff0c;不过…

03-netty基础-多路复用select、poll、epoll

1 什么是多路复用多路复用&#xff08;Multiplexing&#xff09; 是一种让单个线程同时处理多个 I/O 通道的技术&#xff0c;核心是通过系统调用将 I/O 状态查询的工作交给操作系统内核&#xff0c;应用程序只需等待内核通知哪些通道就绪。多路&#xff1a;指的是多个socket网络…

网易大模型算法面经总结第一篇

网友一 MHA的原理&#xff0c;是如何进行加速的&#xff0c;用的什么框架推理。 回答&#xff1a; ①先答一下什么是MHA&#xff1a;Multi-Head Attention&#xff08;MHA&#xff09;是 Transformer 的核心机制&#xff0c;并行地关注输入序列中不同位置的多种信息 ②回答MHA的…

Vue3 面试题及详细答案120道(91-105 )

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

SAP-MM-物料进销存表

ABAP库存进销存报表程序摘要 该ABAP程序是一个完整的库存进销存报表系统,主要功能包括: 报表类型选择: 物料库存进销存 批次库存进销存 寄售库存进销存 供应商库存进销存 原料库存进销存 主要功能: 从历史数据表(MARDH, MSKAH, MSLBH, MCHBH等)获取期初库存 处理物料移动数…

这几天都是发癫写的

#include <iostream> #include <vector> #include <unordered_map> #include <algorithm> #include <cmath> // for sqrt// Gen-Sort 实现&#xff08;保持不变&#xff09; void genSort(std::vector<int>& arr) {if (arr.empty()) r…

QT6 源,七章对话框与多窗体(11) 进度对话框 QProgressDialog:属性,公共成员函数,槽函数,信号函数,与源代码带注释

&#xff08;1&#xff09; 本类的继承关系 &#xff1a;可见&#xff0c;进度对话框&#xff0c;也是 QDialog 的子类&#xff0c;在其上面又摆放了一些控件&#xff0c;构成了不同用途的对话框。咱们也可以自定义对话框。只是没有 QT 官方大师们做的好。 人家在定义这 6 个子…

学习游戏制作记录(技能系统)7.24

1.技能系统概念首先让我们了解一下游戏的技能本质是什么&#xff0c;以投掷剑为例子&#xff0c;当玩家使用这个技能时&#xff0c;首先会播放玩家的动画&#xff0c;随后通过技能脚本创建一个剑的对象&#xff0c;当剑回收时会再次调用脚本&#xff0c;让它朝向玩家飞来并销毁…

外部存档(External Archive)机制

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

MybatisPlus操作方法详细总结

摘要&#xff1a;本文围绕 MyBatis-Plus 数据操作展开&#xff0c;涵盖标准数据层 CRUD 与分页查询&#xff1b;以及各种的复杂 SQL 查询&#xff1b;映射匹配&#xff08;TableField、TableName 注解&#xff09;与 ID 生成策略&#xff08;TableId 五种类型及全局配置&#x…

【C语言进阶】动态内存管理的面试题||练习

本节内容专门整理了一些动态内存管理的面试题&#xff0c;配有详细的解答。 目录 1. 看代码说结果 2. 看代码说结果 3. 看代码说结果 4.小乐乐与欧几里得 描述 分析1&#xff1a; 分析2&#xff1a; 代码&#xff1a; 5. 空心正方形 分析&#xff1a; 1. 看代码说结…

【图论】倍增与lca

void dfs(long u,long father){ dep[u]dep[father]1;//只在这里初始化depfor(long i1;(1<<i)<dep[u];i)fa[u][i]fa[fa[u][i-1]][i-1];//只这里用的倍增for(long ihead[u];~i;iedge[i].next){long vedge[i].to;if(vfather)continue;fa[v][0]u;dfs(v,u); }} long lca(lo…

VS Code 美化插件

目录1. Better Comments 更好的注释2. indent-rainbow 彩虹的缩进3. Trailing Spaces 尾随的空格4. Gruvbox Material 护眼的材质5. Md Editor 博客编辑器6. 待补充推荐笔记&#xff1a;VS Code写代码必备的五款代码美化插件 1. Better Comments 更好的注释 Better Comments Be…

火语言 RPA 在日常运维中的实践

在系统运维和技术支持工作中&#xff0c;总有一些操作像 “固定程序” 一样循环往复&#xff1a;定期检查服务器状态、批量处理用户权限申请、手动清理系统日志…… 这些工作步骤固定、逻辑简单&#xff0c;却占用了大量本可用于故障排查和系统优化的时间。近期在优化运维团队的…