go defer简介

go,defer,简介 · 浏览次数 : 10

小编点评

**代码执行结果:** ``` defer2defer1return 0 ``` **代码分析:** * `test` 函数包含两个 defer 语句和一个 `return` 语句。 * 第一个 `defer` 语句打印 `defer1`。 * 第二个 `defer` 语句打印 `defer2`。 * `return` 语句返回 0。 * `main` 函数调用 `test` 并打印其返回值。 **执行步骤:** 1. `test` 函数注册两个 `defer` 语句,分别打印 `defer1` 和 `defer2`。 2. `test` 函数执行 `return` 语句,返回 0。 3. `main` 函数调用 `test` 并打印其返回值,结果为 `defer2defer1return 0`。 **解释:** * `defer1` 和 `defer2` 在 `test` 函数执行之前被注册。 * `defer1` 打印 `defer1`,因为它在 `test` 函数执行之前执行。 * `defer2` 打印 `defer2`,因为它在 `test` 函数执行之前执行。 * `return` 语句在函数返回后执行,它返回 0。

正文

思考

开始之前,先考虑下下面的代码的执行结果:

package main

import "fmt"

func test() int {
	i := 0
	defer func() {
		fmt.Println("defer1")
	}()
	defer func() {
		i += 1
		fmt.Println("defer2")
	}()
	return i
}

func main() {
	fmt.Println("return", test())
}

defer介绍

defer 是 Go 编程语言中的一个关键字,用于在函数执行结束后延迟执行指定的函数调用。defer 的使用非常灵活,它通常用于执行一些清理操作、资源释放、日志记录等任务。以下是对 defer 的详细介绍:

  1. defer 的语法

    • defer 后面跟随一个函数调用,该函数会在包含 defer 语句的函数执行完毕后被调用。
    • 语法示例:defer someFunction()
  2. 执行时机

    • defer 函数调用会在包含 defer 语句的函数返回之前执行,即使在函数中间有 return 语句也是如此。
    • 这确保了 defer 中的操作在函数结束时始终执行,无论函数是正常返回还是出现异常。
  3. 多个 defer 语句

    • 一个函数可以包含多个 defer 语句,它们会以后进先出(LIFO)的顺序执行。
    • 这意味着最后一个出现的 defer 语句会最先执行,而最先出现的 defer 语句会最后执行。
  4. 常见用途

    • 资源释放defer 常用于关闭文件、释放锁、释放内存等资源管理任务,确保资源在函数结束时得到正确释放。
    • 错误处理defer 可以用于记录错误日志或执行清理操作,以确保即使发生错误,资源也能得到释放。
    • 跟踪代码执行defer 还可以用于记录函数的执行情况,以进行性能分析或跟踪代码路径。
  5. 示例
    下面是一个使用 defer 的示例,演示了文件的打开和关闭操作:

    func readFile(filename string) error {
        file, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer file.Close() // 确保文件在函数返回前关闭
    
        // 文件操作...
    
        return nil
    }
    
  6. 注意事项

    • defer 不仅用于函数的返回,还可以用于方法(类似于面向对象编程中的析构函数)。
    • defer 中的参数会在 defer 语句执行时被求值,因此如果你有多个 defer 语句使用相同的参数,它们会被依次求值。
    • 在某些情况下,要特别小心 defer 中的闭包,以避免出现意外的行为。

defer执行时机

defer 语句中的函数调用会在包含 defer 语句的函数返回之前执行。无论函数是正常返回还是在执行中发生了 panic,defer 中的函数都会按照后进先出(LIFO)的顺序执行。这确保了在函数结束时进行清理和释放资源,以及在函数执行期间处理错误或日志记录等任务。

以下是关于 defer 执行时机的详细解释:

  1. 正常返回时的 defer 执行

    • 在函数执行过程中,当遇到 defer 语句时,不会立即执行 defer 中的函数调用,而是将它们压入一个栈中,以便在函数返回时执行。
    • 当函数执行完毕并准备返回时,栈中的 defer 函数调用会按照后进先出的顺序执行,确保最后一个 defer 最先执行。
  2. 发生 panic 时的 defer 执行

    • 如果函数在执行中发生 panic(异常),同样会执行 defer 中的函数,然后再传播 panic,这允许在 panic 后执行清理操作。
    • 这可以用来释放资源、记录错误信息、关闭连接等。

下面是一个示例,说明了 defer 的执行时机:

func exampleFunction() {
    defer fmt.Println("Deferred 1")
    defer fmt.Println("Deferred 2")

    fmt.Println("Function body")
    panic("Something went wrong")
}

func main() {
    exampleFunction()
}

在这个示例中,exampleFunction 包含两个 defer 语句和一个 panic。当 exampleFunction 调用时,它首先打印 "Function body",然后执行 defer 中的函数。在 panic 发生后,defer 语句中的函数会按照后进先出的顺序执行。所以,main 函数的输出将是:

Function body
Deferred 2
Deferred 1
panic: Something went wrong

正如示例所示,defer 中的函数在函数返回之前或在 panic 发生后都会执行,这使得它在资源管理和错误处理方面非常有用。

结束

现在回到最开始的问题,在上面的代码中,test 函数包含两个 defer 语句,以及一个 return 语句。在 main 函数中,我们调用 test 并输出其返回值。让我们来解释每一步并分析输出的结果:

  1. i 初始化为 0
  2. 第一个 defer 语句中的匿名函数只是打印 "defer1",不对 i 进行任何修改。
  3. 第二个 defer 语句中的匿名函数增加了 i 的值,然后打印 "defer2"。

现在,让我们分析 test 函数的执行流程:

  1. i 初始化为 0
  2. 第一个 defer 语句注册的函数(打印 "defer1")会在函数返回之前执行,但它没有影响 i 的值。
  3. 接下来,第二个 defer 语句注册的函数(增加 i 的值并打印 "defer2")也会在函数返回之前执行,但在执行时,i 的值仍然为 0
  4. return i 语句返回 0

因此,test 函数返回 0,但在执行过程中,两个 defer 函数都被执行,按照注册的顺序分别打印 "defer1" 和 "defer2"。

main 函数中,我们调用 test 并输出其返回值,因此最终的输出是:

defer2
defer1
return 0

这是因为 defer2defer1 的输出分别在 test 函数调用结束之前执行,而 return 0 的结果在函数返回后被 main 函数输出。


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意


与go defer简介相似的内容:

go defer简介

思考 开始之前,先考虑下下面的代码的执行结果: package main import "fmt" func test() int { i := 0 defer func() { fmt.Println("defer1") }() defer func() { i += 1 fmt.Println(

Go 使用原始套接字捕获网卡流量

Go 使用原始套接字捕获网卡流量 Go 捕获网卡流量使用最多的库为 github.com/google/gopacket,需要依赖 libpcap 导致必须开启 CGO 才能够进行编译。 为了减少对环境的依赖可以使用原始套接字捕获网卡流量,然后使用 gopacket 的协议解析功能,这样就省去了解析

Go 如何对多个网络命令空间中的端口进行监听

Go 如何对多个网络命令空间中的端口进行监听 需求为 对多个命名空间内的端口进行监听和代理。 刚开始对 netns 的理解不够深刻,以为必须存在一个新的线程然后调用 setns(2) 切换过去,如果有新的 netns 那么需要再新建一个线程切换过去使用,这样带来的问题就是线程数量和 netns 的数

【建议收藏】Go语言关键知识点总结

容器 数组和切片 在Go语言中,数组和切片是两个基本的数据结构,用于存储和操作一组元素。它们有一些相似之处,但也有许多不同之处。下面我们详细介绍数组和切片的特点、用法以及它们之间的区别。 数组 数组是固定长度的序列,存储相同类型的元素。数组的长度在定义时就固定下来,不能改变。 package mai

如何基于R包做GO分析?实现秒出图

GO分析 基因本体论(Gene Ontology, GO)是一个用于描述基因和基因产品属性的标准术语体系。它提供了一个有组织的方式来表示基因在生物体内的各种角色。基因本体论通常从三个层面对基因进行描述:细胞成分(Cellular Component,CC)、生物学过程(Biological Proc

Go变量作用域精讲及代码实战

关注作者,复旦AI博士,分享AI领域与云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕博,复旦机器人智能实验室成员,国家级大学生赛事评审专家,发表多篇SCI核心期刊学术论文,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人。 精讲

2024-06-05:用go语言,给定三个正整数 n、x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况。 城市中存在一条额外的街道连接房屋 x 和房屋 y。 需要计算对于每个街道数(

2024-06-05:用go语言,给定三个正整数 n、x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况。 城市中存在一条额外的街道连接房屋 x 和房屋 y。 需要计算对于每个街道数(从 1 到 n), 有多少房屋对满足从一个房屋到另一个房屋经过的街道数正好为该街道数。 在结果数组中

go高并发之路——go语言如何解决并发问题

一、选择GO的原因 作为一个后端开发,日常工作中接触最多的两门语言就是PHP和GO了。无可否认,PHP确实是最好的语言(手动狗头哈哈),写起来真的很舒爽,没有任何心智负担,字符串和整型压根就不用区分,开发速度真的是比GO快很多。现在工作中也还是有一些老项目在使用PHP,但21年之后的新项目基本上就都

2024-04-27:用go语言,在一个下标从 1 开始的 8 x 8 棋盘上,有三个棋子,分别是白色车、白色象和黑色皇后。 给定这三个棋子的位置,请计算出要捕获黑色皇后所需的最少移动次数。 需要注意

2024-04-27:用go语言,在一个下标从 1 开始的 8 x 8 棋盘上,有三个棋子,分别是白色车、白色象和黑色皇后。 给定这三个棋子的位置,请计算出要捕获黑色皇后所需的最少移动次数。 需要注意的是,白色车可以垂直或水平移动,而白色象可以沿对角线移动,它们不能跳过其他棋子。 如果白色车或白色象

GO 协程

转载请注明出处: 线程是进程中的一个实体,被系统独立调度和分派的基本单位。线程自己不拥有系统资源,只拥有运行中必不可少的资源。同一进程中的多个线程并发执行,这些线程共享进程所拥有的资源。 协程是一种比线程更加轻量级的存在,重要的是,协程不被操作系统内核管理,协程完全是由程序控制的,不需要手动创建和管