golang学习笔记(defer基础知识)

ops/2024/10/11 9:24:08/

什么是defer

defer语句用于golang程序中延迟函数的调用, 每次defer都会把一个函数压入栈中, 函数返回前再把延迟的函数取出并执行。

为了方便描述, 我们把创建defer的函数称为主函数, defer语句后面的函数称为延迟函数。延迟函数可能有输入参数, 这些参数可能来源于定义defer的函数, 延迟函数也可能引用主函数用于返回的变量, 也就是说延迟函数可能会影响主函数的一些行为。

defer的规则

规则一:延迟函数的参数在defer语句出现时就已经确定

package mainimport "fmt"func main() {deferFuncParameter()
}func deferFuncParameter() {var aInt = 1defer fmt.Println(aInt)aInt = 2return
}

结果:
在这里插入图片描述
代码说明: 函数deferFuncParameter()定义一个整型变量并初始化为1,然后使用defer语句打印出变量值, 最后修改变量值为2。
参考答案: 输出1。 延迟函数fmt.Println(aInt)的参数在defer语句出现时就已经确定了, 后面修改的aInt变量实际上是拷贝了一份。所以无论后面如何修改aInt变量都不会影响延迟函数的执行。
注意: 对于指针类型参数, 规则仍然适用, 只不过延迟函数的参数是一个地址值, 这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。

package mainimport "fmt"func main() {deferArray()
}func printArray(array *[3]int)  {for i := range array {fmt.Println(array[i])}
}func deferArray()  {var aArray = [3]int{1, 2, 3}defer printArray(&aArray)aArray[0] = 10return
}

结果:
在这里插入图片描述
函数说明: 函数deferFuncParameter()定义一个数组, 通过defer延迟函数printArray()的调用, 最后修改数组第一个元素。 printArray()函数接受数组的指针并把数组全部打印出来。
参考答案: 输出10、 2、 3三个值。 延迟函数printArray()的参数在defer语句出现时就已经确定了, 即数组的地址, 由于延迟函数执行时机是在return语句之前, 所以对数组的最终修改值会被打印出来。

规则二:defer延迟函数执行按后进先出顺序执行, 即先出现的defer最后执行

定义defer类似于入栈操作, 执行defer类似于出栈操作。

设计defer的初衷是简化函数返回时资源清理的动作, 资源往往有依赖顺序, 比如先申请A资源, 再跟据A资源申请B资源, 跟据B资源申请C资源, 即申请顺序是:A—>B—>C, 释放时往往又要反向进行。 这就是把deffer设计成FIFO的原因。每申请到一个用完需要释放的资源时, 立即定义一个defer来释放资源是个很好的习惯。

规则三: 延迟函数可能操作主函数的具名返回值

定义defer的函数, 即主函数可能有返回值, 返回值有没有名字没有关系, defer所作用的函数, 即延迟函数可能会影响到返回值。

package mainimport "fmt"func main() {fmt.Println(test())
}
func test() (res int) {a := 1defer func() {res ++}()return a
}

结果
在这里插入图片描述
函数说明: 函数拥有一个具名返回值result, 函数内部声明一个变量a, defer指定一个延迟函数, 最后返回变量a。延迟函数中递增res。
参考答案: 函数输出2。 函数的return语句并不是原子的, 实际执行分为设置返回值—>ret, defer语句实际执行在返回前, 即拥有defer的函数返回过程是: 设置返回值—>执行defer—>res。 所以return语句先把res设置为a的值, 即1, defer语句中又把res递增1, 所以最终返回2。
return 返回值解析
该函数的return语句可以拆分成下面两行:

result = i
return

而延迟函数的执行正是在return之前, 即加入defer后的执行过程如下:

result = i
result++
return

一个主函数拥有一个匿名的返回值, 返回时使用字面值, 比如返回”1”、 ”2”、 ”Hello”这样的值, 这种情况下defer语句是无法操作返回值的。
另外返回值是匿名类型的值,这种情况下defer语句可以引用到返回值, 但不会改变返回值。

package mainimport "fmt"func main() {fmt.Println(test())
}
func test() int {a := 1defer func() {a ++}()return a
}func printArray(array *[3]int)  {for i := range array {fmt.Println(array[i])}
}

结果:
在这里插入图片描述
上面的函数, 返回一个局部变量, 同时defer函数也会操作这个局部变量。 对于匿名返回值来说, 可以假定仍然有一个变量存储返回值, 假定返回值变量为”anony”, 上面的返回语句可以拆分成以下过程:

anony = aa++
return

由于a是整型, 会将值拷贝给anony, 所以defer语句中修改i值, 对函数返回值不造成影响。

总结

  1. defer定义的延迟函数参数在defer语句出时就已经确定下来了
  2. defer定义顺序与实际执行顺序相反
  3. return不是原子操作,执行过程是: 保存返回值(若有)—>执行defer( 若有) —>执行ret跳转
  4. 申请资源后立即使用defer关闭资源是好习惯

http://www.ppmy.cn/ops/13659.html

相关文章

GPT-SoVITS声音克隆训练和推理(新手教程,附整合包)

环境: Win10 专业版 GPT-SoVITS-0421 整合包 问题描述: GPT-SoVITS声音克隆如何训练和推理教程 解决方案: Zero-shot TTS: Input a 5-second vocal sample and experience instant text-to-speech conversion.零样本 TTS:输入 5 秒的人声样本并体验即时文本到语音转换…

《机器学习by周志华》学习笔记-线性模型-02

1、对数几率回归 1.1、背景 上一节我们考虑了线性模型的回归学习,但是想要做分类任务就需要用到上文中的广义线性模型。 当联系函数连续且充分光滑,考虑单调可微函数,令: 1.2、概念 找一个单调可谓函数,将分类任务的真实标记与线性回归模型的预测值联系起来,也叫做「…

JVM支持的可配置参数查看和分类

JVM参数大致可以分为三类: 标注指令:-开头。 这些是所有的HotSpot都支持的参数。可以用java-help 打印出来。 非标准指令: -X开头。 这些指令通常是跟特定的HotSpot版本对应的。可以用java -X打印出来。 不稳定参数: -XX 开头。 这一类参数是跟特定HotSpot版本对应的&#x…

Django项目无法安装python-ldap依赖解决方案

最近工作中安排了一个Python web项目,使用Pycharm从git拉取代码后,配置号Python的解释器和pip后,Pycharm自动下载安装项目所需的依赖,但是有一个依赖django-auth-ldap4.1.0安装始终失败,最初的异常信息提示是&#xff…

闲话 ASP.NET Core 数据校验(一):内置数据校验

前言 所谓输入的是垃圾,输出也必然是垃圾,有多少安全问题隐藏在请求的数据中,所以永远不能相信来自用户端的输入。 对请求数据的合法性进行校验,不仅有助于提升用户界面的友好性,而且有助于提高后台程序的安全性和稳…

01-服务与服务间的通信

这里是极简版,仅用作记录 概述 前端和后端可以使用axios等进行http请求 服务和服务之间也是可以进行http请求的spring封装的RestTemplate可以进行请求 用法 使用bean注解进行依赖注入 在需要的地方,自动注入RestTemplate进行服务和服务之间的通信 注…

ctfshow web入门 SQl注入 web191--web200

web191 多了一个正则绕过 上脚本布尔盲注 用ord #author:yu22x import requests import string url"http://70adf0cb-2208-4974-b064-50a4f4103541.challenge.ctf.show/api/index.php" sstring.ascii_lettersstring.digits flag for i in range(1,45):print(i)for j…

- Single-Spa如何加载、注册和管理子应用

Single-spa是一个用于构建微前端架构的JavaScript框架,它可以加载、注册和管理子应用。 加载子应用: 在主应用中引入Single-spa,通过npm或CDN引入single-spa.js或single-spa.min.js。在主应用的JavaScript文件中,使用import { registerAppl…