Golang基础 函数详解 匿名函数与闭包

news2024/11/18 11:49:19

文章目录

  • 01 匿名函数
    • 1.1 定义匿名函数
    • 1.2 匿名函数使用场景
  • 02 闭包
    • 2.1 闭包实现公有变量
    • 2.2 闭包实现缓存效果
  • 参考资料

匿名函数是指不需要定义函数名的一种函数实现方式(即没有名字的函数)。匿名函数多用于实现回调函数和闭包。


01 匿名函数

Golang 支持匿名函数,即在需要使用函数时,再定义函数;匿名函数没有函数名,只有函数体。

在 Golang 里面,函数可以被作为一种类型被赋值给函数类型的变量进行传递或使用,匿名函数也往往以变量方式被传递。

1.1 定义匿名函数

相较于 C++ 11 中提供的 Lambda 表达式形式来定义匿名函数,Golang 中匿名函数的定义格式除了没有函数名,其他部分与普通函数定义格式一致。换言之,匿名函数的定义就是没有名字的普通函数定义,其格式如下所示:

// 声明格式
func(参数列表)(返回值列表){
    函数体
}

// example:
func main() {
    func1 := func(m, n int) bool {
        return m < n
    }

    a := 1
    b := 2
    if func1(a, b) {
        fmt.Printf("%d<%d\n", a, b)
    } else {
        fmt.Printf("%d>=%d\n", a, b)
    }
}

/* output:
1<2
*/

🅰️将匿名函数赋值给变量:如上例所示,将匿名函数赋值给变量,然后以变量的形式进行传递是较为常见的方式,类似给没有函数名的匿名函数起了个名字。

🅱️在定义时调用匿名函数:匿名函数也可以在定义时直接进行调用执行,不需要外部调用,这样这个函数仅会被调用一次,如下例子所示:

func main() {
    a := 2
    b := 1
    if func(m, n int) bool { return m < n }(a, b) {
        fmt.Printf("%d<%d\n", a, b)
    } else {
        fmt.Printf("%d>=%d\n", a, b)
    }
}

/* output:
2>=1
*/

1.2 匿名函数使用场景

1️⃣匿名函数作为值使用:由于匿名函数没有函数名,这时它就相当于是一条表达式,可以把它赋值给一个变量,在需要使用的地方进行按值调用即可。

func main() {
    func2 := func(s []int) int {
        sum := 0
        for _, v := range s {
            sum += v
        }
        return sum
    }

    fmt.Printf("sqrt 0f func2 is : %f\n", math.Sqrt(float64(func2([]int{1, 2, 3, 3}))))
}

/* output:
sqrt 0f func2 is : 3.000000
*/

2️⃣匿名函数作为回调函数使用

回调函数就是📣通过函数引用调用的函数,例如函数A的参数列表中函数B的引用作为参数之一,并且在函数A中使用该函数引用调用执行函数B,那么函数B就是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件进行响应(回调函数是一种事件驱动机制)。

匿名函数也可以以函数引用的形式作为其他函数的参数使用,📌使用匿名函数作为回调函数的优点在于不需要耗费额外的空间去专门定义处理回调事件的函数📌,因为当函数被执行时才会调用回调函数。

匿名函数作为回调函数需要注意如下两点:

  • 回调函数所必须的值由外部调用函数传入
  • 外部调用函数的参数列表中必须正确声明回调函数
func scanSlice(s []int, f func(int)) {
    for _, v := range s {
        f(v)
    }
}

func main() {
    func3 := func(v int) {
        fmt.Printf(" %d ", v)
    }
    // func3 作为回调函数打印切片中的元素
    scanSlice([]int{1, 2, 3, 4, 5}, func3)
}

/* output:
 1  2  3  4  5 
*/

3️⃣匿名函数用来获取父作用域中的变量:通过匿名函数访问父作用域中的变量是创建闭包的一种常见方式,就是在一个函数内部声明一个匿名函数,通过该匿名函数访问这个函数的局部变量。通过这种方式可以把局部变量留在内存中,避免使用全局变量引发潜在的全部变量污染情况。

02 闭包

闭包的形式化定义是:📌一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。📌

通俗来讲,📣闭包就是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。

👉 函数 + 引用环境 = 闭包 👈

函数和相关引用环境的组合形成了闭包实例,闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

其中,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”。🔴函数是编译期的静态概念,🟢而闭包是运行期的动态概念。(可以与程序和进程的概念进行类比)

在这里插入图片描述

闭包机制的主要用途在于如下两点:

  • 可以获取函数内部的局部变量,实现公有变量
  • 可以让自由变量的值始终保存在内存中,实现缓存效果

2.1 闭包实现公有变量

闭包机制使得函数的内部变量可以在函数外部被重复使用,并且不会造成变量污染。

这相较于全局变量和普通局部变量具有明显优势,虽然全局变量可以重复使用,但是容易造成变量污染;而局部变量仅在局部作用域内有效,虽然不会造成变量污染,但是不可以重复使用。

如下例子中声明了一个累加函数 accumulate,他的返回值是函数类型 func() int,每次调用这个累加函数时,其内部的匿名函数都会对累加函数中的局部变量 x 进行 +1 修改,并打印该变量的地址。在 main 函数中分别声明两个初始值不同的累加函数 func4,func5,并分别调用两次。

func accumulate(n int) func() int {
    var x int = n
    //返回一个自己定义的函数类型 (返回一个闭包:匿名函数 + accumulate引用环境)
    return func() int {
        x++             // 修改accumulate的变量 x
        fmt.Println(&x) // 打印这个变量的地址
        return x        // 返回累加值
    }
}

func main() {
    // 初始值为 1 累加
    func4 := accumulate(1)
    fmt.Printf("%p\n", func4)
    fmt.Println(func4())
    fmt.Println(func4())

    // 初始值为 10 累加
    func5 := accumulate(10)
    fmt.Printf("%p\n", func5)
    fmt.Println(func5())
    fmt.Println(func5())
}

/* output:
0x8df1c0
0xc000012300
2
0xc000012300
3
0x8df1c0
0xc000012308
11
0xc000012308
12
*/

从输出结果可以看出,闭包与函数与引用环境的关系,声明的两个函数他们的地址是一致的,函数中操作的变量地址在同一个闭包中是一致的,然而在不同闭包中变量地址是不一致的。

这表示被捕获到闭包中的变量让闭包本身拥有了📣记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应,其中的变量在闭包的声明周期内变成了“公有变量”。函数在不同引用环境下会形成不同的闭包,所以闭包是一个动态概念。

2.2 闭包实现缓存效果

闭包会把父作用域(函数)中的变量保存在内存中,直到闭包的生命周期结束,所以利用这一特点可以将被闭包捕获的变量作为缓存空间来使用。

如下例子中声明了一个 dish 函数,其自由变量名称为 food 作为缓存空间,其中声明了两个匿名函数,分别实现重置 foodpop 操作和修改 foodpush 操作,用来模拟消费和生产两种行为。🙋‍♂️(由于pop 和 push 的参数列表不一样不好抽象统一,例子中的实现并不优雅,暂时没有想到比较好的实现方式,欢迎指正!)

func dish(s string) map[string]func(string) {
    var food = s
    var function = map[string]func(string){
        "pop": func(newFood string) {
            if food != "" {
                fmt.Printf("pop: %s, pos: %p\n", food, &food)
                food = ""
            } else {
                fmt.Println("the dish is empty!")
            }
        },
        "push": func(newFood string) {
            food = newFood
            fmt.Printf("push: %s, pos: %p\n", food, &food)
        },
    }

    return function
}

func main() {
    func6 := dish("apple")
    func6["pop"]("")
    func6["pop"]("")
    func6["push"]("banana")
    func6["pop"]("")
}

/* output:
pop: apple, pos: 0xc0000465e0
the dish is empty!
push: banana, pos: 0xc0000465e0
pop: banana, pos: 0xc0000465e0
*/

可以看到输出结果中,不管是 pop 还是 push 操作,food 的地址都没有改变过,所以自由变量 food 一直被保存在内存中,并没有在 dish 调用结束后被自动清除,通过这种方式可以实现缓存效果。

但是这种闭包实现缓存的也存在着明显的缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。一种解决方法是在退出函数之前,将不使用的局部变量全部删除。

参考资料

匿名函数 · Go语言中文文档

C++11 Lambda表达式(匿名函数)

匿名函数的应用&命名空间的应用&类与对象的关系与使用方式–2019年9月29日

回调函数(callback)是什么? - 简书

闭包、递归 · Go语言中文文档

闭包的作用 - 简书


如果文章对你有帮助,欢迎一键三连 👍 ⭐️ 💬 。如果还能够点击关注,那真的是对我最大的鼓励 🔥🔥🔥 。


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

财报解读:营收增长、亏损扩大,Shopify如何度过阵痛期?

后疫情时代&#xff0c;Shopify阵痛不断。 图源&#xff1a;Shopify 北京时间2023年2月16日&#xff0c;Shopify披露了2022年四季度财报&#xff0c;营收17.3亿美元&#xff0c;同比增长25.4%&#xff0c;高于分析师预期的16.5亿美元&#xff1b;净亏损为6.24亿美元&#xff0…

用上Visual Studio后,我的世界游戏的构建时间减少了一半

今天我们讲述一个使用 Visual Studio 提升工作效率的案例。 我的世界(Minecraft) 游戏开发商 Mojang Studios 近日联系了 Visual Studio C 团队&#xff0c;因为他们需要将 C 开发扩展到新平台&#xff08;Linux&#xff09;&#xff0c;同时还希望保留他们现有的技术基础&…

同源页面间的跨页面通信之BroadCast Channel

LocalStorage 通过LocalStorage存储内容&#xff0c;并且在改变某个tab页面后&#xff0c;另外一个tab页面监听变动这种方式应该大家都比较熟悉了。 监听变动的代码如下&#xff1a; 第二个tab页面监听如下 window.addEventListener(storage, function (e) {if (e.key ctc…

LeetCode-77. 组合

目录回溯法剪枝优化题目来源 77. 组合 回溯法 1.递归函数的返回值以及参数 在这里要定义两个全局变量&#xff0c;一个用来存放符合条件单一结果&#xff0c;一个用来存放符合条件结果的集合。 List<List<Integer>> result new ArrayList<>();LinkedList…

数据分析就要选择这款免费报表工具

对于一家企业来说&#xff0c;在日常运营的过程中本身就会产出很多的数据&#xff0c;那么这些数据本身就应该形成报表。可是如果只是选择手工的一种操作&#xff0c;确实需要浪费大量的人力物力。伴随着科技进入到快速发展的阶段&#xff0c;市面上更是出现了很多报表工具可以…

九龙证券|可转债一级市场回暖 14家上市公司可转债发行集中获批

可转债商场悄然升温。春节假期后&#xff0c;可转债新券上市体现普遍不错&#xff0c;多只个券首日涨幅打破30%&#xff0c;更有3个买卖日就实现翻倍的案例。一起&#xff0c;本周初可转债打新户数本年以来也首度站上1000万户大关。 因为新券盈余效应明显&#xff0c;可转债一级…

window10安装MySQL数据库

准备好软件MySql的下载参考&#xff1a;(1137条消息) mysql下载与安装过程_weixin_40396510的博客-CSDN博客_mysql数据库下载安装(1137条消息) 安装MySQL的常见问题_二木成林的博客-CSDN博客_sc不是内部或外部命令,也不是可运行的程序解压要C盘&#xff08;自定义&#xff0c;本…

Ubuntu——扩展磁盘空间,可视化软件简单很多

目录 1. 剩余空间查看 2. 虚拟机先分配 3. 安装与使用 gparted 1. 剩余空间查看 2. 虚拟机先分配 关闭虚拟机&#xff0c;打开虚拟机&#xff0c;但不启动&#xff0c;编辑虚拟机设置——点击硬盘- 拓展 设置扩展大小&#xff0c;确定。 但是此时我们的分区和文件 并没有扩容…

阅读笔记3——空洞卷积

空洞卷积 1. 背景 空洞卷积&#xff08;Dilated Convolution&#xff09;最初是为解决图像分割的问题而提出的。常见的图像分割算法通常使用池化层来增大感受野&#xff0c;同时也缩小了特征图尺寸&#xff0c;然后再利用上采样还原图像尺寸。特征图先缩小再放大的过程造成了精…

HummerRisk V0.9.1:操作审计增加百度云,增加主机检测规则及多处优化

HummerRisk V0.9.0发布&#xff1a;增加RBAC 资源拓扑图&#xff0c;首页新增检查的统计数据&#xff0c;云检测、漏洞、主机等模块增加规则&#xff0c;对象存储增加京东云&#xff0c;操作审计增加金山云&#xff0c;镜像仓库新增设置别名。 感谢社区中小伙伴们的反馈&#…

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型

概述 对于surfaceflinger大多数人都知道它的功能是做图形合成的&#xff0c;用英语表示就是指composite。其大致框图如下: 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程surfaceflinger进程中的composition engine与HW…

如何赋能智能运维,迈出数字化黑匣子第一步?

在当下大数据时代&#xff0c;诸多行业专家为企业智能运维绘出美好蓝图。在该蓝图中&#xff0c;互联网、云计算、大数据分析联合发力&#xff0c;企业在能“攻”能“守”中快速、可持续发展。何为“攻”&#xff1f;对支撑企业产品研发、生产、管理、营销等各业务链条的IT基础…

指针数组和数组指针、字符指针

文章目录指针字符指针实例一实例二指针数组实例一实例二实例三数组指针实例一实例二实例三-看书呗指针 1.指针是个变量&#xff0c;用来存放地址&#xff0c;地址将唯一标识一块内存空间 内存编号地址指针 2.指针的大小是固定的&#xff0c;32位平台是4个字节&#xff0c;64位…

【QT】UDP通信QUdpSocket(单播、广播、组播)

目录1. UDP通信概述2. UDP消息传送的三种模式3. QUdpSocket类的接口函数4. UDP单播和广播代码示例4.1 测试说明4.2 MainWindow.h4.3 MainWindow.cpp4.4 界面展示5. UDP组播代码示例5.1 组播的特性5.2 MainWindow.h5.3 MainWindow.cpp5.4 界面展示1. UDP通信概述 UDP是无连接、…

驱动程序开发:基于EC20 4G模块自动拨号联网的两种方式(GobiNet工具拨号和PPP工具拨号)

目录一、EC20 4G模块简介二、根据移远官方文档修改EC20 4G模组驱动  1、因为EC20 4G模组min-pice接口其实就是usb接口&#xff0c;因此需要修改Linux内核源码drivers/usb/serial/option.c文件&#xff0c;如下图&#xff1a;  2、根据USB协议的要求&#xff0c;需要在drive…

从FPGA说起的深度学习(三)

这是新的系列教程&#xff0c;在本教程中&#xff0c;我们将介绍使用 FPGA 实现深度学习的技术&#xff0c;深度学习是近年来人工智能领域的热门话题。在本教程中&#xff0c;旨在加深对深度学习和 FPGA 的理解。用 C/C 编写深度学习推理代码高级综合 (HLS) 将 C/C 代码转换为硬…

linux下安装elasticsearch步骤

linux下安装elasticsearch步骤&#xff1a; 1、Elasticsearch的下载&#xff08;选择7.8.0&#xff09; 1.1、elasticsearch国内社区&#xff1a; https://elasticsearch.cn/1.2、elasticsearch官网地址&#xff1a; https://www.elastic.co/cn/elasticsearch/1.3、elastic…

从零实现Web服务器(二): 线程池以及线程池的作用,Get和Post的区别,项目中如何编写数据库连接池,定时器优化非活跃连接

文章目录一、线程池以及线程池的作用二、手写线程池三、Get和Post的区别四、如何编写数据库连接池五、定时器优化非活跃连接5.1. 基于排序链表实现。5.2. 基于小根堆实现。5.3. 基于红黑树实现。5.4. 基于时间轮实现。5.4.1 单时间轮实现5.4.2 多时间轮实现一、线程池以及线程池…

JavaScript 入门教程||javascript 简介||JavaScript 用法

javascript 简介JavaScript 是互联网上最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。JavaScript 是脚本语言JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代…

C++之lambda函数(匿名函数)

lambda函数简介lambda函数是C11标准新增的语法&#xff0c;也称为lambda表达式或匿名函数。lambda函数的特点是&#xff1a;距离近、简洁、高效和功能强大。优点声明式编程风格&#xff1a;就地匿名定义目标函数或函数对象&#xff0c;有更好的可读性和可维护性。简洁&#xff…