Go基础编程 - 15 - 延迟调用(defer)

news/2024/9/30 0:08:04/

延迟调用 defer

    • 1. 特性
    • 2. 常用用途
    • 3. defer 执行顺序:`同函数内`先进后出
    • 4. defer 闭包
    • 5. defer 陷阱


上一篇:泛型


1. 特性

1. 关键字 defer 用于注册延迟调用。
2. defer 调用直到 return 前才被执行。
3. 同函数内多个 defer 语句,按先进后出的方式执行。
4. defer 语句中的变量,在 defer 声明时就决定了。

2. 常用用途

滥用 defer 可能会造成性能问题,尤其是在一个“大循环”里。

1. 关闭文件句柄。
2. 锁资源释放。
3. 数据库连接释放。

3. defer 执行顺序:同函数内先进后出

package mainimport "fmt"func defer1(i int) {defer fmt.Printf("defer1 %d - 1\n", i)defer fmt.Printf("defer1 %d - 2\n", i)
}func main() {// 函数内 defer,在函数执行结束前执行。// 因此嵌套函数 defer1() 内的 defer 在自身函数内按先进后出的顺序执行。// 且 defer1() 内的 defer 在 main 函数内的执行顺序仅取决于 defer1() 函数的执行顺序。defer defer1(100)defer1(200)		 defer fmt.Println("main 1")defer1(300)			defer defer1(400)	
}

以上代码执行顺序及输出结果如下图:

  1. 函数内 defer,在函数执行结束前执行。
  2. main 函数内,关键字 defer 在 main 函数内按先进后出顺序在 main 函数执行结束前执行,其它代码按顺序执行。
  3. defer1() 函数内关键字 defer 在 defer1 函数内按先进后出顺序执行。
  4. defer1() 函数内的输出在自身函数执行结束前全部输出。
    在这里插入图片描述

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

func main() {defer println(1)defer println(2)i := 0defer func() {println(100 / i)	// 此处抛出 panic,但下面 defer 依然执行}()defer println(3)
}

输出结果

3
2
1
panic: runtime error: integer divide by zero

4. defer 闭包

defer 语句适用于单个函数或语句的延迟执行。当需要执行更复杂的逻辑或者多个操作时,可以使用 defer func () 闭包。

  • 每次执行 defer 语句时,函数值调用的参数都会进行求值并保存,但不执行实际的函数。 也就是复制了一份。

    func main() {i := 1defer fmt.Println("one - i =", i) // i 被复制保存// defer 使用了闭包, 此时仅定义了闭包函数,并未执行。defer func() {fmt.Println("two - i =", i)  // 闭包引用 i}()// 变量 i 作为函数参数传入时,会进行求值并保存。defer func(n int) {fmt.Println("three - i =", n)}(i) // i 被复制保存i = 100defer func(i int) {fmt.Println("four - i =", i)}(i) // i 被复制保存fmt.Println("i =", i)
    }
    

    输出结果:

    i = 100
    four - i = 100
    three - i = 1
    two - i = 100
    one - i = 1
    
  • defer 中可以使用指针或闭包“延迟”读取。

    func main() {x, y, z := 10, 10, 10// 延迟读取 y,z 的值defer func(i int, j *int) {fmt.Printf("defer: x = %d, y = %d, z = %d \n", i, *j, z) // y 指针传递,z 闭包引用}(x, &y) // x 被复制x += 100y += 100z += 100fmt.Printf("main: x = %d, y = %d, z = %d\n", x, y, z)
    }
    

    输出结果:

    main: x = 110, y = 110, z = 110
    defer: x = 10, y = 110, z = 110
    

5. defer 陷阱

  • defer 与闭包、return

    package mainimport ("errors""fmt""os"
    )// 如果 defer 后不是一个闭包,最后执行的时候我们得到的并不是最新的值,而是声明 defer 时保存的值。
    // 有具名返回值函数中,实际值为return前最终计算结果。
    func closure(i, j int) (n int, err error) {defer fmt.Printf("1 n = %d, defer: %v\n", err) // n, err 复制保存当前值defer func(err error) {fmt.Printf("2 n = %d, defer: %v\n", err)}(err) // err 保存当前值传参;n 为全局变量defer func() {fmt.Printf("3 n = %d, defer: %v\n", err)}() // 闭包引用if j == 0 {err = errors.New("divided by zero")return}return i / j, nil
    }// 使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。
    // 1. 示例中打开test.txt、test2.txt都赋值给变量 f。
    // 2. defer 声明使用闭包函数;在执行defer声明的代码时,f 已经被重新赋值为 test2.txt,导致 test.txt 关闭失败。
    //    报错:close failed:test.txt close test2.txt: file already closed
    // 3. 优化建议:避免使用相同变量;或 defer 声明使用值参传参
    func closureCover() {f, err := os.Open("test.txt")if err != nil {fmt.Println("open failed: test.txt")}if f != nil {defer func() {if err := f.Close(); err != nil {fmt.Println("close failed:test.txt", err)}}()// 优化为值传递/*defer func(f *os.File) {...}(f)*/}f, err = os.Open("test2.txt")if err != nil {fmt.Println("open failed: test2.txt")}if f != nil {defer func() {if err := f.Close(); err != nil {fmt.Println("close failed:test2.txt", err)}}() // 优化同上}
    }func main() {closure(2, 0)// 输出:// 3 n = 0, defer: divided by zero// 2 n = 0, defer: <nil>// 1 n = 0, defer: <nil>// 有具名返回值函数中,实际值为return前最终计算结果。输出:// 3 n = 5, defer: <nil>// 2 n = 5, defer: <nil>// 1 n = 0, defer: <nil>
    }
    
  • defer nil 函数

    func main() {defer func() {if err := recover(); err != nil {fmt.Println("recover:", err)}}()var run func() = nildefer run()fmt.Println("running")// 输出:// running// recover: runtime error: invalid memory address or nil pointer dereference
    }
    
  • 在错误位置使用 defer

    package mainimport ("fmt""net/http"
    )func main() {res, err := http.Get("http://www.xxxxxxxxxxx")defer res.Body.Close()if err == nil {return}fmt.Println("running")// 输出// panic: runtime error: invalid memory address or nil pointer dereference
    }
    

    当 http.Get 失败时 res 为 nil,我们在 defer 调用 res.Body 时并未判断是否执行成功,会抛出异常。优化如下:

    func main() {res, err := http.Get("http://www.xxxxxxxxxxx")if err == nil {	 // 判断 http.Get 执行成功时,才需要关闭响应连接defer res.Body.Close()}fmt.Println("running")// 输出// running
    }
    

http://www.ppmy.cn/news/1532159.html

相关文章

GPT-4提示工程大赛冠军的秘籍分享

前言 去年 11 月 8 日&#xff0c;新加坡政府科技局&#xff08;GovTech&#xff09;组织举办了首届 GPT-4 提示工程&#xff08;Prompt Engineering&#xff09;竞赛。数据科学家 Sheila Teo 最终夺冠。之后&#xff0c;Teo 发布了一篇题为《我如何赢得了新加坡 GPT-4 提示工…

数字安全二之密钥结合消息摘要

HMACSHA256的定义 HMACSHA256是一种使用 SHA-256 哈希算法的 HMAC&#xff08;基于哈希的消息认证码&#xff0c;Hash-based Message Authentication Code&#xff09; 机制。它结合了【散列函数】 和 【密钥】&#xff0c;用于确保消息的完整性和真实性 HMAC 与 SHA-256 的作…

[深度学习]卷积神经网络CNN

1 图像基础知识 import numpy as np import matplotlib.pyplot as plt # 图像数据 #imgnp.zeros((200,200,3)) imgnp.full((200,200,3),255) # 可视化 plt.imshow(img) plt.show() # 图像读取 imgplt.imread(img.jpg) plt.imshow(img) plt.show() 2 CNN概述 卷积层convrelu池…

基本数据结构简记

简单记录一下常见的一些数据结构的概念&#xff0c;不包含树和图。 一、线性数据结构 1、主要成员&#xff08;或形式&#xff09; 栈&#xff0c;队列&#xff0c;双端队列&#xff0c;列表 2、特点 有两端 区分方式&#xff1a;元素添加与移除方式 二、栈 1、特点 添加…

行内对齐 vertical-align

MDN vertical-align 在CSS中&#xff0c;文本的垂直对齐通常指的是行内元素&#xff08;inline elements&#xff09;或表格单元格中的内容如何相对于其容器进行上下对齐。对于这些情况&#xff0c;可以使用 vertical-align 属性来控制。 vertical-align 属性适用于以下几种情…

构建网络遇到的问题-AlexNet

1.对模型进行初始化采用的一般代码 def _initialize_weights(self):for m in self.modules(): # 遍历模型每一层if isinstance(m, nn.Conv2d): # 判定m层是否属于nn.Conv2d类型nn.init.kaiming_normal_(m.weight, modefan_out, nonlinearityrelu)if m.bias is not None:nn.in…

专深与广博的平衡艺术

一、引言 ----  随着人工智能&#xff08;AI&#xff09;和生成式人工智能&#xff08;AIGC&#xff09;如ChatGPT、Midjourney、Claude等大语言模型的快速发展&#xff0c;AI辅助编程工具正逐渐成为程序员日常工作的得力助手。这一变革不仅对编程工作方式产生了深远影响&…

在云渲染中3D工程文件安全性怎么样?

在云渲染中&#xff0c;3D工程文件的安全性是用户最关心的问题之一。随着企业对数据保护意识的增强&#xff0c;云渲染平台采取了严格的安全措施和加密技术&#xff0c;以确保用户数据的安全性和隐私性。 云渲染平台为了保障用户数据的安全&#xff0c;采取了多层次的安全措施。…