【Go】Go语言中延迟函数、函数数据的类型、匿名函数、闭包等高阶函数用法与应用实战

news2024/12/23 1:55:55

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • 函数进阶
    • 1. 延迟函数(defer)
    • 2. 函数的数据类型
    • 3. 函数的本质
    • 4. 匿名函数
    • 5. 回调函数
    • 6. 闭包
      • 闭包计算斐波那契数

函数进阶

1. 延迟函数(defer)

defer语句在Go语言中是一个强大的特性,它允许你延迟函数的执行直到包含它的函数即将返回。这通常用于清理资源、解锁互斥锁、记录时间、关闭文件等操作。
比如说我们打开一个文件,我们判断它有没关闭,我们希望所有代码执行完,最后调用defer函数来关闭文件

语法格式:
defer 函数调用
注意:

defer语句会将其后的函数调用压入一个栈中,当外层函数即将返回时,这些被defer的函数会按照后进先出(LIFO)的顺序执行。
defer可以在函数中多次使用,以注册多个需要延迟执行的函数。

package main

import "fmt"

// defer
func main() {
    f("1")
    fmt.Println("2")
    //其他语句执行完,再执行defer包含的函数
    defer f("3")
    fmt.Println("4")
}

func f(s string) {
    fmt.Println(s)
}

可以看到3最后打印
在这里插入图片描述
流程分析
在这里插入图片描述

defer函数或者方法:一个函数或方法的执行被延迟了

  • 你可以在函数中添加多个defer语句当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时i/o 流,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题
  • 如果有很多调用 defer,那么 defer 是采用 后进先出(栈) 模式。
  • 你可以在函数中添加多个defer语句,当没有加defer的语句执行完毕后,这些defer语句会按照逆序执行

多个defer案例:

package main

import "fmt"

// defer 作用:处理一些善后的问题,比如错误,文件、网络流关闭等等操作。
// 特点,多个defer的问题
// 不加defer的语句还是按顺序从上而下执行
// 你可以在函数中添加多个defer语句,当没有加defer的语句执行完毕后,这些defer语句会按照逆序执行
func main() {
    f("1")
    defer fmt.Println("2")
    defer f("3")
    fmt.Println("4")
    defer f("5")
    fmt.Println("6")
    defer f("7")
    fmt.Println("8")
}

func f(s string) {
    fmt.Println(s)
}

在这里插入图片描述

defer存在传递参数:
在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了

package main

import "fmt"

// defer传参的调用时机
func main() {
    n := 10
    fmt.Println("main n=", n)
    // 在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了
    defer ff(n) // 问题,defer延迟执行函数,参数时什么时候传递进去?在其他地方虽然n已经加1了,ff函数最后执行,但是ff函数中的n还是10,说明参数是先加载进来,只是没执行函数
    n++
    fmt.Println("main end n=", n)

    defer ff(n) //如果ff在n加1之后defer延迟执行,参数中的n是加1后的n
}

func ff(n int) {
    fmt.Println("ff函数中n=", n)
}

在这里插入图片描述

defer后续常用场景:程序会报错: 异常(程序执行的时候突然报错了)、错误(我们开发的时候知道的预期错误)

善后工作:defer 处理异常。

2. 函数的数据类型

  • func (xxxx,xxx) xxx,xxxx
  • 函数也是一种数据类型,可以定义函数类型的变量
package main

import "fmt"

// 函数是什么(数据类型)
func main() {
    a := 10.01
    fmt.Printf("%T\n", a) // 查看变量的类型
    b := [4]int{1, 2, 3, 4}
    fmt.Printf("%T\n", b) // 查看变量的类型
    c := true
    fmt.Printf("%T\n", c) // 查看变量的类型

    // 函数的类型
    func1()                   // 带了括号是函数的调用
    fmt.Printf("%T\n", func1) // 查看变量的类型 func()
    fmt.Printf("%T\n", func2) // 查看变量的类型 func(int, int, ...string) (int, int)
    // func(int, int) (int, int)
    // func(int, int, ...string) (int, int)
    //var fun3 func(int, int, ...string) (int, int)
    fun3 := func2
    r1, r2 := fun3(1, 2, "111")
    fmt.Println(r1, r2)
    // 函数在Go语言中本身也是一个数据类型,加了() 是调用函数,不加(), 函数也是一个变量,可以赋值给别人。

    // 函数的类型就等于该函数创建的类型,他也可以赋值给
}

// 无参无返回值的函数
func func1() {

}

// 有参有返回值的函数
func func2(a, b int, c ...string) (int, int) {
    return 0, 0
}

在这里插入图片描述

3. 函数的本质

函数在Go语言中不是一个简单的调用或者接收结果的。

函数在Go中是一个符合类型,可以看做是一个特殊的变量。var 定义吗,赋值。类型相同即可

函数类型的样子 :var f1 函数名(参数) 结果

变量名:指向一段内存 (num --> 0x11111aaaa)

函数名:指向一段函数体的内存地址,是一种特殊类型的变量。我们可以将一个函数赋值给另外一个类型相同的函数
在这里插入图片描述

4. 匿名函数

Go语言支持匿名函数,即没有名称的函数。匿名函数通常用于实现局部的、一次性的功能,或者作为高阶函数的参数传递。
匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。
将匿名函数进行赋值,就可以实现多次调用。

Go语言支持函数式编程

  • 将匿名函数作为另外一个函数的参数,回调函数
  • 将匿名函数作为另外一个函数的返回值,可以形成闭包结构
package main

import "fmt"

// 匿名变量 (没有名字的变量)
// 匿名函数 (没有名字的函数)
func main() {
    // 正常的调用
    f12()
    f2 := f12 // 函数赋值给另外一个函数
    f2()
    // f12  f2 本质指向了同一个内存空间,空间中的代码一致  {fmt.Println("我是f12函数")}

    // 匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。
    func() {
        fmt.Println("我是一个匿名函数1")
    }()

    // 将匿名函数进行赋值,就可以实现多次调用。
    f3 := func() {
        fmt.Println("我是一个匿名函数2")
    }
    //将匿名函数赋值给变量后,可以直接通过变量调用
    f3()

    // 匿名函数是否可以添加参数和返回值
    func(a, b int) {
        fmt.Println("a,b")
    }(1, 2)

    // 将匿名函数的返回值定义给变量。
    r1 := func(a, b int) int {
        return a + b
    }(1, 2)
    fmt.Println(r1)

    // 由于Go语言中的函数是一个特殊的变量,支持匿名操作
    // Go语言支持函数式编程
    // - 将匿名函数作为另外一个函数的参数,回调函数
    // - 将匿名函数作为另外一个函数的返回值,可以形成闭包结构

}
func f12() {
    fmt.Println("我是f12函数")
}

在这里插入图片描述

5. 回调函数

高阶函数:可以将一个函数作为另外一个函数的参数。

fun1()

fun2(fun1)

fun1 函数作为 fun2 函数的参数

fun2函数,叫做高阶函数,接收了另外一个函数作为参数的函数

fun1函数,叫做回调函数,作为另外一个函数的参数

package main

import "fmt"

// 回调函数
func main() {
    // 函数调用
    r1 := add(1, 2)
    fmt.Println("r1是:", r1)
    // 高阶函数调用
    r2 := oper(1, 2, add)
    fmt.Println("r2是:", r2)
    r3 := oper(1, 2, sub)
    fmt.Println("r3是:", r3)

    // 匿名函数
    fun1 := func(a, b int) int {
        return a * b
    }
    r4 := oper(1, 2, fun1) // 调用匿名函数 *
    fmt.Println("r4是:", r4)

    // 能够直接传递匿名函数
    r5 := oper(8, 2, func(a int, b int) int {
        if b == 0 {
            fmt.Println("除数不能为0")
            return 0
        }
        return a / b
    })
    fmt.Println("r5是:", r5)
}

// 运算 (运算的数字,运算操作)
// 高阶函数,参数是接收另外一个函数
func oper(a, b int, fun func(int, int) int) int {
    //打印出的fun是每次传入的函数内存地址
    fmt.Println(a, b, fun)
    r := fun(a, b)
    return r
}

func add(a, b int) int {
    return a + b
}
func sub(a, b int) int {
    return a - b
}

在这里插入图片描述

函数也可作为可变参数传参
在这里插入图片描述

6. 闭包

一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。

局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用

package main

import "fmt"

// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。

// 什么时候用闭包: js (xxxxxxx.html   引用大量的第三方库:10个js库,js库中很多变量名是冲突的)
// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染

// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。

/*
闭包结构:
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。

在闭包结构中:局部变量的生命周期就会发生改变,
正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.

// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。
*/

// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。
var i int = 10

func main() {
    r1 := increment()
    fmt.Println(r1) // 返回的是一个 increment() 内层函数,还没有执行
    // -- 执行这个内层函数
    //
    v1 := r1()
    fmt.Println(v1)
    v2 := r1()
    fmt.Println(v2)
    fmt.Println(r1())
    fmt.Println(r1())
    fmt.Println(r1())
    // 你写的代码是对的,但是结果不对,你的变量被污染了
    fmt.Println("--------------------------")

    // r2和r1指向同一个地址
    r2 := increment() // 再次调用的时候 ,i = 0
    v3 := r2()
    fmt.Println(v3) // 1

    //因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1
    fmt.Println(r1()) // 6     // 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
    fmt.Println(r2()) // 2
    // r1 名字 ----> 内存地址 &r1
    fmt.Printf("%p\n", &r1)
    fmt.Printf("%p\n", &r2)

}

// 自增函数
// increment() 函数返回值为  func() int 类型
func increment() func() int { // 外层函数,项目(很多的全局变量)
    // 定义一个局部变量
    i := 0
    // 在外层函数内部定义一个匿名函数,给变量自增并返回。
    fun := func() int {
        i++
        return i
    }
    return fun
}

在这里插入图片描述

如果我们想使用闭包结构来解决全局变量污染的问题,那我们就可以写一个闭包结构来创建执行的函数。

通过这个闭包结构创建的函数内部的变量,都在这个函数中作用,不会和其他函数冲突。

**闭包结果的返回值是一个函数。**这个函数可以调用闭包结构中的变量。

package main

import "fmt"

// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。

// 什么时候用闭包: js (xxxxxxx.html   引用大量的第三方库:10个js库,js库中很多变量名是冲突的)
// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染

// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。

/*
闭包结构:
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。

在闭包结构中:局部变量的生命周期就会发生改变,
正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.

// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。
*/

// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。
var i int = 10

func main() {
    r1 := increment()
    fmt.Println(r1) // 返回的是一个 increment() 内层函数,还没有执行。直接打印函数名得到的是名字的地址
    // -- 执行这个内层函数
    //
    v1 := r1()
    fmt.Println(v1)
    v2 := r1()
    fmt.Println(v2)
    fmt.Println(r1())
    fmt.Println(r1())
    fmt.Println(r1())
    // 你写的代码是对的,但是结果不对,你的变量被污染了
    fmt.Println("--------------------------")

    // r2和r1指向同一个地址
    r2 := increment() // 再次调用的时候 ,i = 0
    v3 := r2()
    fmt.Println(v3) // 1

    //因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1
    fmt.Println(r1()) // 6     // 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
    fmt.Println(r2()) // 2
    // r1 名字 ----> 内存地址 &r1
    fmt.Printf("%p\n", &r1)
    fmt.Printf("%p\n", &r2)

}

// 自增函数
// increment() 函数返回值为  func() int 类型
//通过increment()可以创建无数个函数,让incremnet()变量不会影响每个函数的使用

func increment() func() int { // 外层函数,项目(很多的全局变量)
    // 定义一个局部变量
    i := 0
    // 在外层函数内部定义一个匿名函数,给变量自增并返回。
    fun := func() int {
        i++
        return i
    }
    return fun
}

在这里插入图片描述

闭包计算斐波那契数

package main

import "fmt"

// 返回一个“返回类型为int的函数” ,将闭包函数作为值返回
func fibonacci() func() int {
    a, b := 1, 1 //定义初始的两个值
    return func() int {
        c := a
        a, b = b, a+b
        return c
    }
}

func main() {
    f := fibonacci()
    //计算前10个斐波那契数列
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

在这里插入图片描述

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

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

相关文章

每日处理250亿个事件,Canva如何应对数据洪流

在这个数据被称为“新石油”的时代&#xff0c;如何有效地处理海量信息流显得尤为重要。作为广受欢迎的设计平台&#xff0c;Canva不仅因其用户友好的界面而备受关注&#xff0c;还因其高效利用Amazon Kinesis管理每日高达250亿个事件而成为热议焦点。让我们深入探讨Canva是如何…

【案例70】invalid secrity token(null)

问题现象 系统登录时提示invalid secrity token(null) 问题分析 排查发现令牌种子没有配置或被人为修改 解决方案 1、登录环境。代码路径下bin下有个sysconfig.bat。左侧选“系统配置”。右侧点“安全”。读取保存一下。 2、或者找一个好用的环境。把ierp/bin下的prop.xml文…

Redis简介、常用命令及优化

文章目录 一、关系数据库​​与非关系型数据库概述1. 关系型数据库2. 非关系型数据库3.关系数据库与非关系型数据库区别 二、Redis简介1.Redis的单线程模式2.Redis 优点3.Redis 缺点 三、安装redis四、Redis 命令工具五、Redis 数据库常用命令六、Redis 多数据库常用命令七、Re…

【算法专题--回文】最长回文子串 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、预备知识 &#x1f95d; 什么回文串 &#xff1f; 四、题目解析 五、总结与提炼 六、共勉 一、前言 最长回文子串 这道题&#xff0c;可以说是--回文专题 --&#xff0c;最经典的一道题&#xff0c;也是在面试中频率最高…

哈希表和双向链表实现LRU

LRU&#xff08;Least Recently Used&#xff09;即最近最少使用&#xff0c;是一种内存管理算法。最近在Linux的缓冲区管理也看到了使用LRU算法&#xff0c;即利用哈希表进行 O(1) 复杂度的快速查找&#xff0c;利用双向链表&#xff08;里面的元素是缓冲头&#xff09;对缓冲…

再次进阶 舞台王者 第八季完美童模全球赛代言人【吴浩美】赛场+秀场超燃合集

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕。在盛大的颁奖典礼上&#xff0c;一位才能出众的少女——吴浩美迎来了她舞台生涯的璀璨时刻。 代言人——吴浩美&#xff0c;以璀璨童星之姿&#xff0c;优雅地踏上完美童模盛宴的绚丽舞台&#xff0c;作为开…

【趣学Python算法100例】兔子产子

问题描述 有一对兔子&#xff0c;从出生后的第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子&#xff0c;假设所有的兔子都不死&#xff0c;问30个月内每个月的兔子总对数为多少&#xff1f; 题目解析 兔子产子问题是一个有趣的古典数学问题&#xff0c…

Office关闭安全提示

每次启动都要提示这个&#xff0c;怎么关&#xff1f;

大数据-135 - ClickHouse 集群 - 数据类型 实际测试

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数

title: Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数 date: 2024/9/14 updated: 2024/9/14 author: cmdragon excerpt: 通过使用 Nuxt Kit 的自动导入功能,您可以更高效地管理和使用公共函数、组合式函数和 Vue API。无论是单个导入、目录导入还是从第三方模块导入…

GMB外链是什么?

GMB外链其实就是百万外链&#xff0c;它是一种通过大量反向链接来提升网站页面权重的方法。如果你刚建了一个新网站&#xff0c;想在短时间内被收录并获得排名&#xff0c;GMB外链能帮你做到这点。它不像传统SEO那样需要等待好几个月的效果&#xff0c;GMB外链能在24小时内帮你…

vector(2)

前言 通过上一节的学习&#xff0c;我们知道了vector中可以存放各种类型的数据&#xff0c;这就意味着vector之中不仅仅可以存放int、char等内置类型&#xff0c;还可以存放vector和string等类型&#xff0c;我们结合底层的具体情况来具体分析 vector的复用&#xff08;套娃&a…

光控资本:股票增发是什么意思?股票增发的形式?

股票增发配售是已上市的公司通过指定投资者&#xff08;如大股东或组织投资者&#xff09;或全部投资者额定发行股份搜集资金的融资办法。 留意&#xff1a;股票增发后&#xff0c;股价会除权下降。由于增发后股本扩大了&#xff0c;那么每股收益与每股净资产均下降&#xff0…

今天一次讲明白C++条件变量

在C中&#xff0c;std::condition_variable 条件变量是一个同步原语&#xff0c;它允许一个或多个线程在某个条件成立时&#xff0c;被另一个线程唤醒。std::condition_variable 条件变量通常与互斥锁&#xff08;std::mutex&#xff09;一起使用&#xff0c;以保护共享数据和同…

David Baker 任科学顾问,初创公司发布世界最大蛋白质相互作用数据库,已获 8 轮融资

蛋白质-蛋白质相互作用 (Protein-Protein Interactions, PPI) 是细胞生命活动的重要组成部分&#xff0c;在调控和维持细胞的生理功能中&#xff08;如细胞的信号传导、代谢反应和基因表达&#xff09;发挥着不可或缺的作用。 然而目前 PPl 数据库中的数据相对较少&#xff0c…

穿越病毒区-第15届蓝桥省赛Scratch中级组真题第2题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第187讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

CCF201912_1

题解&#xff1a; #include<bits/stdc.h>using namespace std;int n;bool shouldSkip(int num) {if (num % 7 0){return true;}while (num > 0){if (num % 10 7){return true;}num / 10;}return false; } int main() {scanf("%d", &n);int b[4] { 0…

Android Studio 安装配置教程(Windows最详细版)

目录 前言 Android Studio 下载 Android Studio 安装 Android Studio 使用 一、创建默认项目&#xff08;Compose&#xff09; 二、创建常规项目 三、使用ViewBinding 四、查看Gradle版本、SDK版本、JDK版本 ① Gradle版本 ② SDK版本 ③ JDK版本 前言 Android开发…

跟《经济学人》学英文:2024年09月14日这期 The sweet story of Peru’s blueberry boom

The sweet story of Peru’s blueberry boom Plucky farmers have transformed the market in only ten years plucky&#xff1a;英 [ˈplʌki] 勇敢的&#xff1b;无畏的&#xff1b;有胆识的 原文&#xff1a; Peru’s blueberry harvest is just beginning, and Ivan Ja…

自动驾驶:LQR、ILQR和DDP原理、公式推导以及代码演示(七、CILQR约束条件下的ILQR求解)

&#xff08;七&#xff09;CILQR约束条件下的ILQR求解 CILQR&#xff08;(Constrained Iterative Linear Quadratic Regulator)&#xff09; 是为了在 iLQR 基础上扩展处理控制输入和状态约束的问题。在这种情况下&#xff0c;系统不仅要优化控制输入以最小化代价函数&#x…