当前位置: 首页 > article >正文

golang-defer延迟机制

defer延迟机制

defer是什么

defer是go中一种延迟调用机制。

执行时机

defer后面的函数只有在当前函数执行完毕后才能执行。

执行顺序

将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源。

多个defer本质就是用栈存储,先进后出。

defer定义

//最后不要忘记函数调用
//使用匿名函数
defer func (形参列表){}(实参)//最后不要忘记调用函数
//使用事先声明的函数
defer 函数名(实参)//最后不要忘记调用方法
defer 方法名(形参列表){}(实参)

defer的功能一般是用于释放资源。

defer后面的函数是可以有返回值的,但是一般没有作用。

注意事项

方法或函数必需调用

//报错:未调用函数
defer func(){fmt.Println("a")
}

注意声明顺序

虽然defer的执行时机在函数结束后,但是声明的时候使用的变量或者参数得是函数内在defer声明之前就定义好的。

//报错:student未定义
defer student.GetName(2)
var student Student//报错:age未定义
defer func(a int){fmt.Printf("年龄为%d\n",age)
}(age)
age := 15

多个defer的执行顺序

多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO).

写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下:

package defer_knowledgeimport "fmt"type Student struct{Name string
}func (s Student) GetName(n int){fmt.Printf("这是第%d个defer\n",n)
}func sayHello(n int){fmt.Printf("这是第%d个defer\n",n)
}//验证defer的执行顺序
func DeferFirst(){fmt.Println("hello world")var age int = 25defer func (){fmt.Println("我是第1个defer")}()age++/*虽然defer的执行时机在return之后但是声明defer时,结构体实例要先声明,否则无法访问结构体实例方法*/var student Studentdefer student.GetName(2)defer func (){fmt.Println("我是第3个defer")}()defer sayHello(4)
}

结果

这是第4个defer
我是第3个defer
这是第2个defer
我是第1个defer

图示
在这里插入图片描述

延迟参数传入时机

基本语法

注意事项

defer函数的入参参数是在defer函数声明时决定的。例如

package defer_knowledgeimport "fmt"//defer的参数是声明时传入的
func DeferParams(){var age = 10defer func(a int){fmt.Printf("defer内的参数为%d\n",a)}(age)age = 25fmt.Printf("age已经变成了%d\n",age)
}

调用结果

age已经变成了25
defer内的参数为10

小结

值类型

所以我们要注意传入的参数,

【值类型参数】
值类型参数原始变量改变不影响传入参数,例如int、数组、结构体
如果我们想要defer执行时能读取到变化后的"值类型"参数,可以传入指针

例如

package defer_knowledge
import "fmt"
//defer的参数是声明时传入的
func DeferParams(){var age = 10//如果想要追踪值类型的变化可以传入值类型指针defer func(a *int){fmt.Printf("最初如果传入指针,defer内参数为%d\n",*a)}(&age)defer func(a int){fmt.Printf("defer内的参数为%d\n",a)}(age)age = 25fmt.Printf("age已经变成了%d\n",age)
}

调用结果

age已经变成了25
defer内的参数为10
最初如果传入指针,defer内参数为25
引用类型

具体看引用的底层是否发生变换,例如切片,如果没发生扩容将使用相同的。

package defer_knowledge
import "fmt"
func DeferParams2(){var arr = make([]int,5,5)//引用类型直接传递即可,将追踪到引用改变为止defer func(a []int){fmt.Printf("defer内的参数为%#v\n",a)}(arr)arr[2] = 10fmt.Printf("arr已经变成了%#v\n",arr)
}

调用结果

arr已经变成了[]int{0, 0, 10, 0, 0}
defer内的参数为[]int{0, 0, 10, 0, 0}

声明时机和执行时机

声明时机

defer的声明时机时按照他出现在代码中的顺序,这时会执行两个操作。

1.传入参数
2.检查内部要访问的变量是否已经定义

举例

func DeferTime(){var age int/*声明时会传入参数,以及检查内部逻辑是否正确*/defer func(){//注意,这个不是defer函数的参数//和常规变量作用域一样,本层找不到就去外面找age++}()
}

错误示范

func DeferTime(){defer func(){//报错:age未定义age++}()var age int
}

执行时机

defer的执行时机是在函数逻辑结束后,或者说return后,按照defer栈调用。

举例

func DeferTime2(){var age int defer func(){//按照defer栈,此时访问到的age为 11age = age+5fmt.Printf("age的值为%d\n",age)}()defer func(){//defer执行时机为函数结束后,所以此时访问到的 age = 10age++fmt.Printf("age的值为%d\n",age)}()age = 10
}

结果

age的值为11
age的值为16

defer与return的区别

图示

在这里插入图片描述

可以看到 return 执行的时候,并不是原子性操作,一般是分为两步:将结果x赋值给了返回值,然后执行了RET指令;而defer语句执行的时候,是在赋值变量之后,在RET指令之前。所以这里注意一下。返回值和x的关系。如果x是一个值类型,这里是进行了拷贝的。

执行图示意

在这里插入图片描述

函数返回值

不具名返回

形式

func 函数名(参数列表) 返回值类型{return 返回值
}//例如
/*return sum操作拆解:实际对外暴露返回值为 sum_copysum_copy = sum所以return实际执行拷贝操作,他不是将函数内的变量抛出,而是将拷贝后的值抛出
*/
func Add(a,b int) int{sum := a+breturn sum
}

案例1

func DeferAndReturn1() int{var num intdefer func(){num++//num的值为16fmt.Printf("num的值为%d\n",num)}()num = 15return num
}//调用
target := DeferAndReturn1()
//target的值为15
fmt.Printf("target的值为%d\n",target)

解析

func DeferAndReturn1() int{var num intdefer func(){num++//num的值为16fmt.Printf("num的值为%d\n",num)}()num = 15/*实际操作copy_num = num对外暴露copy_num,由于num是值类型,所以后续defer中对num的操作不影响copy_num*/return num
}

误区1

想到了指针操作,但是理解出错。

func DeferAndReturn1() int{var num intvar ptr = new(int)ptr = &numdefer func(){*ptr++//num的值为16fmt.Printf("num的值为%d\n",num)}()num = 15return num
}
//调用
target := DeferAndReturn1()
//target的值为15
fmt.Printf("target的值为%d\n",target)

原因:

func DeferAndReturn1() int{var num intvar ptr = new(int)ptr = &numdefer func(){*ptr++//num的值为16fmt.Printf("num的值为%d\n",num)}num = 15/*实际操作copy_num = num对外暴露copy_num,我们修改通过指针ptr修改num的值,还是没影响到copy_num*/return num
}

正确思维

func DeferAndReturn1() *int{var num intvar ptr = new(int)ptr = &numdefer func(){num++//num的值为16fmt.Printf("num的值为%d\n",num)}()num = 15return ptr
}
//调用
target := DeferAndReturn1()
//target的值为16
fmt.Printf("target的值为%d\n",*target)

结果

num的值为16
target的值为16

原因

func DeferAndReturn1() *int{var num intvar ptr = new(int)ptr = &numdefer func(){num++//num的值为16fmt.Printf("num的值为%d\n",num)}()num = 15/*实际操作copy_ptr = ptr由于ptr是引用类型,所以defer对ptr的影响会影响到copy_ptrnum的本质就是*ptr,操作num就是在操作ptr*/return ptr
}
具名返回

相当于实际要暴露的返回值早就确定好了,return只是起到一个结束函数的作用。

func 函数名(参数列表)(返回值 返回值类型){return
}//示例
/*sum就是实际暴露的返回值,且已经声明了
*/
func Add(a,b int) (sum int){return
}

案例1

func DeferAndReturn2() (num int){defer func(){num++}()num = 10return
}num2 := DeferAndReturn2()
fmt.Printf("外部num的值为%d\n",num2)

结果

外部num的值为11

原因

func DeferAndReturn2() (num int){defer func(){num++}()num = 10/*这里写return 和 return num一样最终暴露的值为 num所以defer中对num的操作会影响到最终返回值*/return
}

相关文章:

golang-defer延迟机制

defer延迟机制 defer是什么 defer是go中一种延迟调用机制。 执行时机 defer后面的函数只有在当前函数执行完毕后才能执行。 执行顺序 将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执…...

【小沐学Web3D】three.js 加载三维模型(Angular)

文章目录 1、简介1.1 three.js1.2 angular.js 2、three.js Angular.js结语 1、简介 1.1 three.js Three.js 是一款 webGL(3D绘图标准)引擎,可以运行于所有支持 webGL 的浏览器。Three.js 封装了 webGL 底层的 API ,为我们提供了…...

一种替代DOORS在WORD中进行需求管理的方法 (二)

一、前景 参考: 一种替代DOORS在WORD中进行需求管理的方法(基于WORD插件的应用)_doors aspice-CSDN博客 二、界面和资源 WORD2013/WORD2016 插件 【已使用该工具通过第三方功能安全产品认证】: 1、 核心功能 1、需求编号和跟…...

一个基于ragflow的工业文档智能解析和问答系统

工业复杂文档解析系统 一个基于ragflow的工业文档智能解析和问答系统,支持多种文档格式的解析、知识库管理和智能问答功能。 系统功能 1. 文档管理 支持多种格式文档上传(PDF、Word、Excel、PPT、图片等)文档自动解析和分块处理实时处理进度显示文档解析结果预览批量文档…...

23种设计模式-行为型模式-访问者

文章目录 简介场景解决完整代码核心实现 总结 简介 访问者是一种行为设计模式,它能把算法跟他所作用的对象隔离开来。 场景 假如你的团队开发了一款能够使用图像里地理信息的应用程序。图像中的每个节点既能代表复杂实体(例如一座城市)&am…...

WebView2最低支持.NET frame4.5,win7系统

WebView2最低支持.NET frame什么版本 ‌WebView2 对 .NET Framework 的最低版本要求‌ ‌基础支持范围‌ WebView2 官方支持的 .NET Framework ‌最低版本为 4.5‌,同时兼容 ‌.NET Core 3.0‌ 及以上版本‌18。对于 WPF、WinForms 等桌面应用开发,需确…...

WHAT - React 组件的 props.children 属性

目录 一、什么是 children二、基本用法三、类型定义(TypeScript)四、一些高级用法1. 条件渲染 children2. 多个 children 插槽(命名插槽) 五、children 的优势总结 在 React 中,children 是一个非常重要且特殊的 内置属…...

组播网络构建:IGMP、PIM 原理及应用实践

IP组播基础 组播基本架构 组播IP地址 一个组播IP地址并不是表示具体的某台主机,而是一组主机的集合,主机声明加入某组播组即标识自己需要接收目的地址为该组播地址的数据IP组播常见模型分为ASM模型和SSM模型ASM:成员接收任意源组播数据&…...

建筑兔零基础自学记录69|爬虫Requests-2

Requests库初步尝试 #导入requests库 import requests #requests.get读取百度网页 rrequests.get(http://www.baidu.com) #输出读取网页状态 print(r.status_code) #输出网页源代码 print(r.text) HTTP 状态码是三位数字,用于表示 HTTP 请求的结果。常见的状态码有…...

NVIDIA PhysX 和 Flow 现已完全开源

NVIDIA PhysX SDK 在 3-Clause BSD 许可下开源已有六年半了,但其中并非所有内容都是开源的。直到最近,随着 GPU 模拟内核源代码在 GitHub 上的发布,这种情况才有所改变。以下是 NVIDIA 分享的消息,以及 Flow SDK 着色器实现的发布…...

QML面试笔记--UI设计篇01常用控件分类

1. QML常用控件深度解析:从入门到实战2. 控件分类全景图3. 核心控件详解 3.1. 布局控件(构建界面骨架) 3.1.1. ▶ ColumnLayout 3.2. 交互控件 3.2.1. ▶ 智能搜索框(组合控件) 3.3. 数据可视化控件 3.3.1. ▶ 动态仪表…...

电脑DNS出错无法打开网页

目录 解决步骤 打开“控制面板”--》“查看网络状态和任务” 打开“更改适配器设置” 对WLAN右键,打开属性 打开“使用下面的DNS服务器地址”--》高级 添加“114.114.114.114”,点击确定 今天晚上突然网页打不开了,一开始我以为是网络的…...

[Redis]redis-windows下载安装与使用

本篇记录windows redis下载安装与使用。 下载 官网下载方式(没windows版) https://redis.io/downloads/#stack 可以选择下载社区版Redis CE与增强版Redis Stack。 两者都不支持直接运行在windows上,需要Docker环境。 You can install Redis CE locally on your …...

Python-Django+vue宠物服务管理系统功能说明

❥(^_-) 上千个精美定制模板,各类成品Java、Python、PHP、Android毕设项目,欢迎咨询。 ❥(^_-) 程序开发、技术解答、代码讲解、文档,💖文末获取源码+数据库+文档💖 💖软件下载 | 实战案例 💖文章底部二维码,可以联系获取软件下载链接,及项目演示视频。 本项目…...

极氪汽车云原生架构落地实践

云原生架构落地实践的背景 随着极氪数字业务的飞速发展,背后的 IT 技术也在不断更新迭代。极氪极为重视客户对服务的体验,并将系统稳定性、业务功能的迭代效率、问题的快速定位和解决视为构建核心竞争力的基石。 为快速响应用户的需求,例如…...

2025年AI开发学习路线

目录 一、基础阶段(2-3个月) 1. 数学与编程基础 2. 机器学习入门 二、核心技能(3-4个月) 1. 深度学习与框架 2. 大模型开发(重点) 三、进阶方向(3-6个月) 1. 多模态与智能体…...

网络出故障时,四大表(MAC表、ARP表、路由表、转发表)怎么查?看看这套排查顺序

网络出故障时,四大表 (MAC表、ARP表、路由表、转发表) 怎么查 说正题之前,我们先来假设一个场景: 场景假设: 一台华为设备突然上不了网,或者访问某个 IP 不通。 你会怎么排查? 别慌,兄弟&a…...

数据结构与算法-图论-复习1(单源最短路,全源最短路,最小生成树)

1. 单源最短路 单一边权 BFS 原理:由于边权为单一值,可使用广度优先搜索(BFS)来求解最短路。BFS 会逐层扩展节点,由于边权相同,第一次到达某个节点时的路径长度就是最短路径长度。 用法:适用…...

oracle 动态性能视图

Oracle 数据库中的 V$SQLAREA 是一个动态性能视图(Dynamic Performance View),用于记录共享池(Shared Pool)中所有 SQL 语句的统计信息。每个 SQL 语句在共享池中存储为一个游标(Cursor)&#x…...

Vue3+Vite+TypeScript+Element Plus开发-10.多用户动态加载菜单

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 Header响应式菜单缩展 Mockjs引用与Axios封装 登录设计 登录成功跳转主页 多用户动态加载菜单 Pinia持久化 动态路由-配置 文章目录 目录 系列文档目…...

前端用户列表与后端分页协同设计

分页实现方案 在现代Web应用中,用户列表展示与分页是一个常见的功能需求。前端与后端通过API协同工作,使用PageHelper等工具实现高效分页。 例如: 后端实现 (使用PageHelper) public PageResult DishPage(DishPageQueryDTO dishPageQuery…...

三月份面试感触

我毕业三年了,也在公司干了三年本来还以为很快的找到工作,没想到呀现在就业环境是真的差,那个boss和智联一堆的外包找你,找你要了简历然后就没下文了,我也去面了几家自研的公司,只能说这不是欺负老实人吗&a…...

C++使用WebView2控件,通过IPC通信与Javascript交互

引言 在现代桌面应用程序开发中,Web技术与原生应用的融合变得越来越普遍。Microsoft的WebView2控件为C开发者提供了一个强大的工具,使他们能够在桌面应用中嵌入基于Chromium的Web浏览器引擎。本文将详细介绍如何在C应用程序中使用WebView2控件&#xff…...

精准测试建设过程中遇到的一些问题

1.sqlite3 仅可以处理单个任务问题,多线程往往会面临数据库锁定 因为仅临时存储,后来在创建数据库时,给每个任务开了一个临时数据库,存储数据执行完毕后,删除db sql_insert_new:INSERT INTO analyze_api_resault_dynam…...

【Docker】Dockerfile 编写实践

👻创作者:丶重明 👻创作时间:2025年4月8日 👻擅长领域:运维 目录 1. Dockerfile编写原则1.1.选择合适的基础镜像1.2.镜像层优化1.3.多阶段构建1.4.安全增强 2. 关键指令与技巧2.1.COPY vs ADD2.2.ENTRYPOIN…...

Jakarta EE 11发布:云原生Java企业应用的新标准

📝 摘要 Jakarta EE 11于2023年正式发布,这是Java企业版技术栈的一次重要更新。本文将详细介绍Jakarta EE 11的核心特性、改进之处以及如何利用这些新功能构建现代化的云原生应用。我们将通过实际代码示例展示新特性的使用方法,并分析其对Ja…...

蓝桥杯第十五届C++B组省赛真题解析

蓝桥杯第十五届CB组省赛真题解析 一、宝石组合https://www.lanqiao.cn/problems/19711/learning/ 解题思路 题目要求找到三个数,使得它们的最大公约数(GCD)尽可能大,并在GCD相同的情况下选择数值最小的三个数。以下是分步解析&a…...

LabVIEW商业软件开发注意问题

在 LabVIEW 商业软件开发进程中,性能优化、界面设计及兼容性与扩展性,对软件品质、用户体验和市场适配性起着决定性作用。下面,借助多个LabVIEW 编程特性的实际案例,深入分析这些方面的开发要点。 一、性能优化:提升软…...

面试算法高频04-分治与回溯

分治与回溯 分治和回溯算法,包括其概念、特性、代码模板,并结合具体题目进行讲解,旨在帮助学员理解和掌握这两种算法的应用。 分治与回溯的概念 分治(Divide & Conquer):本质上基于递归,先…...

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法 报错内容尝试第一次解决方法尝试第二次解决方法注意事项参考连接 报错内容 Unable to download server on client side: Error: Request downloadRequest failed unexpectedly without providing any details… Will tr…...