golang 闭包函数的应用技巧

2023-11-02

一、有名函数和匿名函数

函数变量类型初始值为nil。函数字面量类型的语法表达格式是 func (InputTypeList) OutputTypeList

//无参函数
func fun() {   
}
var f func()//无入参无返回值的函数对象声明,初始值为nil
f = fun
//有参函数
type FT func(int)
func Fa(int){}
func Test(FT){}
Test(Fa) //pass function as parameter

“有名函数”和“匿名函数(没有函数名类似闭包)”的类型都属于函数字面量类型,有名函数的定义相当于初始化一个函数字面量类型后将其赋值给一个函数名变量,“匿名函数”的定义也是直接初始化一个函数字面量类型,只是没有绑定到一个具体函数名变量上。从 Go 类型系统的角度来看,“有名函数”和“匿名函数”都是函数字面量类型的实例。可以使用 type NewType OldType 语法定义一种新类型,这种类型都是命名类型,同理可以使用该方法定义一种新类型——函数命名类型,简称函数类型,例如:type NewFuncType FuncLiteral 依据Go语言类型系统的概念,NewFuncType 为新定义的函数命名类型,FuncLiteral 为函数字面量类型,FuncLiteral 为函数类型 NewFuneType 的底层类型。

type CalculateType func(int, int) // 声明了一个函数类型
// 该函数类型实现了一个方法
func (c *CalculateType) Serve() {
  fmt.Println("我是一个函数类型")
}
// 加法函数
func add(a, b int) {
  fmt.Println(a + b)
}
// 乘法函数
func mul(a, b int) {
  fmt.Println(a * b)
}
func main() {
  a := CalculateType(add) // 将add函数强制转换成CalculateType类型
  b := CalculateType(mul) // 将mul函数强制转换成CalculateType类型
  a(2, 3)
  b(2, 3)
  a.Serve()
  b.Serve()
}

二、方法作为函数变量传递

当特定对象实例的方法method作为函数指针时传递时,接受者会保证在调用的时候,调用到是这个对象实例的method,method的任何操作都会针对该对象实例生效,而且不需要传任何类似于this、self指针之类的东西,换句话说,对象实例+method 作为绑定的整体传递给接受者的

type Outer struct{
    a string
}
func (o *Outer)Hello(in *Inner){
   fmt.Println("Inner:",in,"\tcall\tOuter:",o.a)
}
type Inner struct{
        a string
    cb Cb
}
type Cb func(*Inner)
func (in *Inner)Register(cb Cb){
    in.cb = cb
}
func (in *Inner)Say(){
    in.cb(in)
}

func main() {
    out1 := Outer{a:"out1 instance"}
    out2 := Outer{a:"out2 instance"}
    fmt.Println("out1 :",&out1)
    fmt.Println("out2 :",&out2) 
    in1 := Inner{a:"int1 instance"}
    in1.Register(out1.Hello)
    in1.Say()
    in1.Register(out2.Hello)
    in1.Say()
}

三、闭包构成的三种情况

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)。

  • 闭包里没有引用环境(变量生命周期很短,调用完即释放)

    // 第一种场景
    func fib01() func() int {
    	return func() int {
    		a, b := 0, 1
    		a, b = b, a+b
    		return a
    	}
    }
    
  • 闭包里引用全局变量(变量生命周期就是全局变量生命周期)

    var y int
    // 第二种场景
    func fib00() func() int {
    	return func() int {
    		y++
    		return y
    	}
    }
    
  • 闭包里引用局部变量(变量生命周期长,调用完不释放,下次调用会继续引用,相当于变相延长了函数的生命周期),闭包可能会导致变量逃逸到堆上来延长变量的生命周期,给 GC 带来压力。

    func AntherExFunc(n int) func() {
        n++
        return func() {
            fmt.Println(n)
        }
    }
    
    func ExFunc(n int) func() {
        return func() {
            n++
            fmt.Println(n)
        }
    }
    
    func main() {
        myAnotherFunc:=AntherExFunc(20)
        fmt.Println(myAnotherFunc)  //0x48e3d0  在这儿已经定义了n=20 ,然后执行++ 操作,所以是21 。
        myAnotherFunc()     //21 后面对闭包的调用,没有对n执行加一操作,所以一直是21
        myAnotherFunc()     //21
        myFunc:=ExFunc(10)
        fmt.Println(myFunc)  //0x48e340   这儿定义了n 为10
        myFunc()       //11  后面对闭包的调用,每次都对n进行加1操作。
        myFunc()       //12
    }
    

四、for循环中的并发闭包:

//因为for语句里面中闭包使用的v是外部的v变量,当执行完循环之后,v最终是c,所以如果在主协程执行完for之后,定义的子协程才开始执行结果可能是ccc,
//如果for过程中,子协程先执行了,结果就可能不是c, c,c”。 
func test1() {                
    s := []string{"a", "b", "c"}                             
    for _, v := range s { 
        go func() {
            fmt.Println(v)
        }()                 
    }                        
    time.Sleep(time.Second * 1)                                                       
}
//for程序如果想输出a,b,c的解决方法:
//只需要每次将变量v的拷贝传进函数即可,但此时就不是使用的上下文环境中的变量了。
func test2() {                
    s := []string{"a", "b", "c"}                             
    for _, v := range s { 
        go func(v string) {
            fmt.Println(v)
        }(v)   //每次将变量 v 的拷贝传进函数                 
    }                        
    select {}                                                      
}

//结果为4 4 4 4 4,函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4
func test1() {
    var users [5]struct{}
    for i := range users {
        defer func() { fmt.Println(i) }()
    }
}
//defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份利用函数参数拷贝,输出为 4 3 2 1 0
func test1() {
    var users [5]struct{}
    for i := range users {
        defer Print(i)
    }
}
func Print(i int) {
    fmt.Println(i)
}

select语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select会随机地选取其中之一执行。select {}会一直阻塞。很多时候我们需要让main函数不退出,利用select {}让它在后台一直执行

func main() {
    for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息
        c := consumer.New()
        go c.Start()
    }
    select {} // 阻塞
}

五、闭包的应用场景和作用:

1、延迟调用,关键字 defer 用于注册延迟调用。defer 调用会在当前函数执行结束前才被执行,这些调用被称为延迟调用 。defer 中使用匿名函数依然是一个闭包。

func main() {
    x, y := 1, 2
    defer func(a int) { 
        fmt.Printf("x:%d,y:%d\n", a, y)  // y 为闭包引用
    }(x)      // 复制 x 的值
    x += 100
    y += 100
    fmt.Println(x, y)
}

2、变量空间隔离,避免变量污染外部无法对闭包引用的上下文变量进行直接操作,保证了私有性。

// 函数计数器  利用闭包每个计数器有自己独立未暴露的sum
func counter(f func()) func() int {  
	sum := 0
	return func() int {
		f()
		sum += 1
		return sum
	}
}

// 测试的调用函数
func foo() {
	fmt.Println("call foo")
}

func main() {
	cnt := counter(foo)
	cnt()
	cnt()
	cnt()
	fmt.Println(cnt())
}
/*
输出结果:
call foo
call foo
call foo
call foo
4
*/
---
//加法器
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	myAdder := adder()
	// 从1加到10
	for i := 1; i <= 10; i++ {
		myAdder(i)
	}
	fmt.Println(myAdder(0))
	// 再加上45
	fmt.Println(myAdder(45))
}
---

//斐波那契闭包处理
func fibonacci() func() int {
	b0 := 0
	b1 := 1
	return func() int {
		tmp := b0 + b1
		b0 = b1
		b1 = tmp
		return b1
	}

}

func main() {
	myFibonacci := fibonacci()
	for i := 1; i <= 5; i++ {
		fmt.Println(myFibonacci())
	}
}

3、装饰函数:函数是Go语言的一等公民,可以作为函数的参数进行传递。装饰器指的就是函数作为参数进行传递的情况。用于构造函数中对对象的封装和处理(尤其是成员较多的结构体)。闭包函数中本身是没有定义变量,而是引用了它所在的环境中的变量。

// Options 调用参数  被装饰的结构体
type Options struct {
  ...
  Discovery            discovery.Discovery
  LoadBalance          loadbalance.LoadBalancer
  CircuitBreaker       circuitbreaker.CircuitBreaker
}

// Option 调用参数工具函数
type Option func(*Options)


// WithDiscovery 指定服务发现
func WithDiscovery(d discovery.Discovery) Option {

  return func(o *Options) {
  	o.Discovery = d
  }
}
...//其它类似的装饰函数省略。。。。

// selector默认实现,内部自动串好 服务发现 负载均衡 熔断隔离 等流程
type Selector struct{}
// Select 输入service name,返回一个可用的node
func (s *Selector) Select(serviceName string, opt ...Option) (*registry.Node, error) {
  ...
  opts := &Options{
  	Discovery:      discovery.DefaultDiscovery,
  	LoadBalance:    loadbalance.DefaultLoadBalancer,
  	CircuitBreaker: circuitbreaker.DefaultCircuitBreaker,
  }
  for _, o := range opt {
  	o(opts)
  }
  ....
}

//newSelector
func newSelector(registry  discovery.DefaultDiscovery)*registry.Node {
  selector := &Selector{}
  n, err := selector.Select("configServer",WithDiscovery(registry ))
  ....
  return n
}

4、闭包引用的上下文变量会延长生命期,避免再次传递。

//普通函数:传递根据哪个后缀判断,其次是文件名字
func makeSuffix (suffix string, name string) string {
    if !strings.HasSuffix(name, suffix) {
        return name + suffix  //如果没有后缀就拼接
    }
    return name

} 
func main(){
    fmt.Println("文件名处理后:", makeSuffix("jpg","go语言圣经"))  
    fmt.Println("文件名处理后:", makeSuffix("jpg","PHP设计模式.jpg"))
}
---
//闭包函数,生成函数可以传入一个文件名,如果该文件名没有指定的后缀(如.jpg),则返回.jpg,如果有则全称
func makeSuffix (suffix string) func (string) string {
    return func (name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix  //如果没有后缀就拼接
        }
        return name
    }
} 
func main(){
    //先返回一个闭包
    test := makeSuffix(".jpg")
    fmt.Println("文件名处理后:", test("go语言圣经"))  
    fmt.Println("文件名处理后:", test("PHP设计模式.jpg"))

}

5、内嵌匿名函数的并发调用,原因代码逻辑有for循环或处理时间较长的逻辑需要匿名函数包住另起协程运行,从而不阻塞影响后续逻辑。或者有defer等调用次序的问题需要在匿名函数中先调用defer等。

func main() {
    ch := make(chan struct{})
    go func() {
        fmt.Println("do something..")
        for {
        	 // process
		}
        time.Sleep(time.Second * 1)
        ch <- struct{}{}
    }()

    <-ch
    fmt.Println("I am finished")
}

6.拦截器的使用之中间件模式

多个中间件会形成一个栈结构(middle stack),以"先进后出"(first-in-last-out)的顺序执行,被称为洋葱结构。
在这里插入图片描述

如洋葱模型所示:调用请求先被第一个filter处理,然后依次交给后续的filter,最后交给业务逻辑处理。处理完之后又从内层的filter一层一层向外层返回,最后返回给客户端。请求先一层一层地进入洋葱,再一层一层地出来,这两个地方都可以写逻辑,也就是所谓的hook。先执行fitler函数,其中最后执行的是业务逻辑函数。

Handle的logic函数其实就是洋葱的中心——业务逻辑,这个在调用Handle时由框架设置好。然后Handle内部是一个递归调用的闭包,如果所有filter都执行完了,就执行logic函数,如果后面还有filter就执行filter.

// Chain 链式过滤器
type Chain []Filter

// Handle 链式过滤器递归处理流程
func (fc Chain) Handle(ctx context.Context, req interface{}, rsp interface{}, logic HandleFunc) (err error) {

	n := len(fc)
	curI := -1
	var chainFunc HandleFunc
	chainFunc = func(ctx context.Context, req interface{}, rsp interface{}) error {
		if curI == n-1 {
			return logic(ctx, req, rsp)
		}
		curI++
		err := fc[curI](ctx, req, rsp, chainFunc)
		return err
	}

	return chainFunc(ctx, req, rsp)
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

golang 闭包函数的应用技巧 的相关文章

  • golang中默认的HTTP拨号超时值

    我正在运行 golang http 客户端来对服务器进行压力测试 有时我会收到错误 拨号 tcp 161 170 xx xxx 80 操作超时 错误 我认为这是 HTTP 客户端超时 我正在考虑增加超时值https stackoverflo
  • 如何从 golang fyne 容器中删除对象

    我正在开发 GUI 应用程序 需要动态添加和删除 gui 元素 我想知道是否有办法从 golang fyne 容器中删除元素 在下面的示例代码中 我创建了容器并动态添加元素 现在我希望能够删除这些元素而不是隐藏它们 我尝试的一个 解决方案
  • 在 Go 中解析 RFC-3339 / ISO-8601 日期时间字符串

    我尝试解析日期字符串 2014 09 12T11 45 26 371Z 在围棋中 该时间格式定义为 RFC 3339 日期时间 https datatracker ietf org doc html rfc3339 section 5 6
  • 在 Go 中,如何将函数的 stdout 捕获到字符串中?

    例如 在 Python 中 我可以执行以下操作 realout sys stdout sys stdout StringIO StringIO some function prints to stdout get captured in t
  • 正则表达式不匹配

    我正在尝试以下代码 d byte x01 x00 x00 x00 x00 x00 x00 x00 x00 x00 x00 x80J x13 x80SQ x80L xe0 x80 x92 x80L x80H xe0 r regexp Must
  • 无法将字符串解组为 int64 类型的 Go 值

    我有结构 type tySurvey struct Id int64 json id omitempty Name string json name omitempty I do json Marshal在 HTML 页面中写入 JSON
  • 在 Go 中读取请求负载?

    我正在使用文件上传器 需要请求负载中的详细信息来裁剪它 func Upload w http ResponseWriter r http Request reader err r MultipartReader if err nil htt
  • 将 []string 传递给需要可变参数的函数

    为了不一遍又一遍地重复我的自我 我想创建一个处理运行一些命令的函数 func runCommand name string arg string error cmd exec Command name arg if err cmd Run
  • 如何自定义解析错误的 HTTP 400 响应?

    我编写了一个 REST API 服务 要求所有响应均为 JSON 但是 当 Go HTTP 请求解析器遇到错误时 它会返回 400 作为纯文本响应 而不会调用我的处理程序 例子 gt curl i H Authorization Basic
  • GoLang - 坚持使用 ISO-8859-1 字符集

    我正在开发一个项目 我们需要将信息保存在具有 ISO 8859 1 表的旧数据库中 因此 在向数据库写入内容之前 我需要将其从 UTF 8 转换为 ISO 8859 1 每次从数据库检索它时 我都需要将其转换回 UTF 8 我试图使用图书馆
  • 无法理解 5.6.1。注意事项:捕获迭代变量

    我正在学习 Go 但无法理解 var rmdirs func for dir range tempDirs os MkdirAll dir 0755 rmdirs append rmdirs func os RemoveAll dir NO
  • 当变量更新时动态刷新模板的一部分golang

    在Golang中 当变量更新时可以刷新模板的一部分吗 例如 我们可以在 Angular js 中找到这一点 基本上在我的代码中 我通过 ajax 中的邮政编码查找地址 它显示我找到的该邮政编码的用户列表 Here is a sample o
  • 给定方法值,获取接收者对象

    Go 有没有办法从方法值获取接收者对象 例如有没有这样的MagicFunc这将使以下程序输出字符串my info来自底层 Foo 实例 package main import fmt type Foo struct A string fun
  • 是否支持动态变量?

    我想知道Go中是否可以动态创建变量 我在下面提供了一个伪代码来说明我的意思 我将新创建的变量存储在切片中 func method slice make type for i 0 i lt 10 i var variable i i slic
  • 在golang中获取TTFB(第一个字节的时间)值

    我正在尝试获取 TTFB 值和 Connect 值 c exec Command curl w Connect time connect TTFB time starttransfer Total time time total o dev
  • 如何在 Golang 中将 []byte XML 转换为 JSON 输出

    有没有办法在 Golang 中将 XML byte 转换为 JSON 输出 我有以下功能body is byte但我想在一些操作之后将此 XML 响应转换为 JSON 我试过了Unmarshal in xml打包没有成功 POST func
  • 在 Go 中使用电子邮件地址创建证书签名请求 (CSR)

    我尝试使用 crypto x509 包生成 CSR 但没有找到将 emailAddress 字段添加到其主题中的方法 根据文档证书申请 http golang org pkg crypto x509 CertificateRequest结构
  • 错误“binary.Write:无效类型”是什么意思?

    下面显示的代码 我创建了一个结构类型并希望将其编码为二进制 但它显示binary Write invalid type main Stu错误 我读过一些类似的代码 但我找不到为什么我的代码不起作用 type Stu struct Name
  • 为什么 Go 中只有 int 而没有 float?

    在 Go 中 有这样的类型int这可能相当于int32 or int64取决于系统架构 我可以声明一个整数变量而不用担心它的大小 var x int 为什么没有这个类型float 这相当于float32 or float64取决于我的系统架
  • 编写每个处理程序中间件

    我希望从处理程序中提取一些重复的逻辑 并将其放入一些每个处理程序的中间件中 特别是 CSRF 检查 检查现有会话值 即身份验证或预览页面 等 我读了关于此的几篇文章 http justinas org writing http middle

随机推荐

  • JVisualVM初步使用

    JVisualVM初步使用 1 前言 jvm调优工具有常见的为Jconsole jProfile VisualVM Jconsole 为jdk自带 功能简单 但是可以在系统有一定负荷的情况下使用 对垃圾回收算法有很详细的跟踪 JProfil
  • 学习HTML的知识点总结

    一 网页 1 什么是网页 网站是指在因特网上根据一定规律 使用HTML等制作用于展示特定内容的网页集合 网页是网站中的一 页 通常是HTML格式的文件 他要通过浏览器来阅读 网页是构成网站的基本元素 它通常由图片 链接 文字 声音 视频等元
  • 【大模型】更强的 LLaMA2 来了,开源可商用、与 ChatGPT 齐平

    大模型 可商用且更强的 LLaMA2 来了 LLaMA2 简介 论文 GitHub huggingface 模型列表 训练数据 训练信息 模型信息 许可证 参考 LLaMA2 简介 2023年7月19日 Meta 发布开源可商用模型 Lla
  • 合并有序数组

    合并两个有序数组 描述 给你两个有序整数数组 nums1 和 nums2 请你将 nums2 合并到 nums1 中 使 num1 成为一个有序数组 说明 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 你可以假设 num
  • Pytest+selenium+allure+Jenkins自动化测试框架搭建及使用

    一 环境搭建 1 Python下载及安装 Python可应用于多平台包括windows Linux 和 Mac OS X 本文主要介绍windows环境下 你可以通过终端窗口输入 python 命令来查看本地是否已经安装Python以及Py
  • 软件测试22种测试方法与详解

    黑盒测试 不基于内部设计和代码的任何知识 而是基于需求和功能性 白盒测试 基于一个应用代码的内部逻辑知识 测试是基于覆盖全部代码 分支 路径 条件 单元测试 最微小规模的测试 以测试某个功能或代码块 典型地由程序员而非测试员来做 因为它需要
  • 用js制作一个视觉差背景

    我在网上冲浪的时候看到了一个文字和背景下滑速度不一致的情况 这看起来背景会有一种3d的感觉 于是研究了一下 首先先写出大概的html和css div class box div class bg div h2 我是一个文字 h2 p 我是一
  • 算法实验题1

    第一题 由1 3 4 5 7 8这6个数字组成六位数中 能被11整除的最大的数是多少 解答 可以使用暴力枚举法 将1 3 4 5 7 8的所有排列组合情况求出来 判断它们是否能被11整除 然后取其中能被11整除的最大值 但是这个方法的时间复
  • 蓝桥杯 第6天 动态规划(4)

    目录 1 121 买卖股票的最佳时机 力扣 LeetCode leetcode cn com 1 暴力解法 2 动态规划 2 122 买卖股票的最佳时机 II 力扣 LeetCode leetcode cn com 3 123 买卖股票的最
  • uni-app 页面样式

    页面样式与布局 尺寸单位 uni app 支持的通用 css 单位包括 px rpx px 即屏幕像素 rpx 即响应式px 一种根据屏幕宽度自适应的动态单位 以750宽的屏幕为基准 750rpx恰好为屏幕宽度 屏幕变宽 rpx 实际显示效
  • C++整数转成二进制方法总结

    经常遇到要用到二进制的情况 这里我就记录下 1 逐次经典位操作 返回一个含有二进制数的vector include
  • 【深度学习之图像理解】图像分类、物体检测、物体分割、实例分割、语义分割的区别

    Directions in the CV 物体分割 Object segment 属于图像理解范畴 那什么是图像理解 Image Understanding IU 领域包含众多sub domains 如图像分类 物体检测 物体分割 实例分割
  • 前端zip.js实现加密打包上传文件

    背景 一方面 部分系统对文件的私密性和安全性要求较高 实现前端加密打包 服务端不存储密码 下载时手动输入密钥并解压文件 另一方面 传输压缩包到客户端 节约了带宽 节约了传输时间 使用的库 zip js Support of the Zip6
  • List写入Excel,poi操作

    前言 公司最近需要将所有的报表导出集中到报表中心系统中 需要做一个通用的Excel工具类 让各个业务系统简单高效的生成Excel报表 由于原先各个业务系统生成报表方式都不一样 有的地方还直接使用了CSV 因此需要统一生成Excel 本来想用
  • Android Studio 可以正常编译运行 但是代码爆红

    这段时间毕设选题 选了一个自己曾经做过的题目 因为之前是用Android Studio2 3 3写的 现在导入Android Studio 3 2 1 代码报错 但是能正常编译运行 很是奇怪 主要报错原因是 找不到有些类 之前用Androi
  • zookeeper(二)——2PC理论、zookeeper集群、ZAB 协议

    一 关于 2PC 提交 Two Phase Commitment Protocol 当一个事务操作需要跨越多个分布式节点的时候 为了保持事务处理的 ACID特性 就需要引入一个 协调者 TM 来统一调度所有分布式节点的执行逻辑 这些被调度的
  • CTFshow php特性 web111

    目录 源码 思路 题解 总结 源码
  • adworld-crypto-banana_princess

    拿到了一个打不开的pdf文件 用hex editor打开一下看看 再看一下正常的pdf文件 猜测是用了rot13映射了一下字母字符 解密脚本 def load data filename content with open filename
  • SpringBoot线程上下文传递数据

    1 底层实现 使用ThreadLocal 使用方法 public T get public void set T value public void remove 2 自定义上下文 package com ybw context confi
  • golang 闭包函数的应用技巧

    一 有名函数和匿名函数 函数变量类型初始值为nil 函数字面量类型的语法表达格式是 func InputTypeList OutputTypeList 无参函数 func fun var f func 无入参无返回值的函数对象声明 初始值为