专栏首页林冠宏的技术文章Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点

Golang, 以 9 个简短代码片段,弄懂 defer 的使用特点

作者:林冠宏 / 指尖下的幽灵

掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

博客:http://www.cnblogs.com/linguanh/

GitHub : https://github.com/af913337456/

腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities

虫洞区块链专栏:https://www.chongdongshequ.com/article/1536563643883.html


前序

deferGo语言中一个很重要的关键词。本文主要以简短的手法列举出,它在不同的多种常见代码片段中,所体现出来的不一样的效果。从笔试的角度来看,可以说是覆盖了绝大部分题型。

此外,在本文之前,还有本人另一篇同样使用例子的形式channel 数据类型做直观讲解的文章。

Golang, 以17个简短代码片段,切底弄懂 channel 基础

目录

  • defer 的主要特点
  • 非引用传参给defer调用的函数,且为非闭包函数情况
  • 传递引用给defer调用的函数,即使不使用闭包函数情况
  • 传递值给defer调用的函数,且非闭包函数情况
  • defer调用闭包函数,且内调用外部非传参进来的变量的情况
  • defer调用闭包函数,若内部使用了传参参数的值的情况
  • defer所调用的非闭包函数,参数如果是函数的情况
  • defer 不影响 return的值
  • 闭包函数对 defer 的影响

defer 的主要特点

  • 延迟调用
  • 所在的函数中,它在 returnpanic执行完毕 后被调用
  • 多个 defer,它们的被调用顺序,为的形式。先进后出,先定义的后被调用
func Test_1(t *testing.T) {
    // defer 的调用顺序。由下到上,为 栈的形式。先进后出
    defer0()   // ↑
    defer1()   // |
    defer2()   // |
    defer3()   // |
    //defer4() // |
    defer5()   // |
    defer6()   // |  
    defer7()   // |
    defer8()   // |  从下往上
}

非引用传参给defer调用的函数,且为非闭包函数,值不会受后面的改变影响

func defer0() {
    a := 3  // a 作为演示的参数
    defer fmt.Println(a) // 非引用传参,非闭包函数中,a 的值 不会 受后面的改变影响
    a = a + 2
}
// 控制台输出 3

传递引用给defer调用的函数,即使不使用闭包函数,值也受后面的改变影响

func myPrintln(point *int)  {
    fmt.Println(*point) // 输出引用所指向的值
}
func defer1() {
    a := 3
    // &a 是 a 的引用。内存中的形式: 0x .... ---> 3
    defer myPrintln(&a) // 传递引用给函数,即使不使用闭包函数,值 会 受后面的改变影响
    a = a + 2
}
// 控制台输出 5

传递值给defer调用的函数,且非闭包函数,值不会受后面的改变影响

func p(a int)  {
    fmt.Println(a)
}

func defer2() {
    a := 3
    defer p(a) // 传递值给函数,且非闭包函数,值 不会 受后面的改变影响
    a = a + 2
}
// 控制台输出: 3

defer调用闭包函数,且内调用外部非传参进来的变量,值受后面的改变影响

// 闭包函数内,事实是该值的引用
func defer3() {
    a := 3
    defer func() {
        fmt.Println(a) // 闭包函数内调用外部非传参进来的变量,事实是该值的引用,值 会 受后面的改变影响
    }()
    a = a + 2  // 3 + 2 = 5
}
// 控制台输出: 5
// defer4 会抛出数组越界错误。
func defer4() {
    a := []int{1,2,3}
    for i:=0;i<len(a);i++ {
        // 同 defer3 的闭包形式。因为 i 是外部变量,没用通过传参的形式调用。在闭包内,是引用。
        // 值 会 受 ++ 改变影响。导致最终 i 是3, a[3] 越界
        defer func() {
            fmt.Println(a[i])
        }()
    }
}
// 结果:数组越界错误

defer调用闭包函数,若内部使用了传参参数的值。使用的是值

func defer5() {
    a := []int{1,2,3}
    for i:=0;i<len(a);i++ {
        // 闭包函数内部使用传参参数的值。内部的值为传参的值。
        defer func(index int) {
            fmt.Println(a[index]) // index == i
        }(i)
        // 后进先出,3 2 1
    }
}
// 控制台输出: 
//     3
//     2
//     1

defer所调用的非闭包函数,参数如果是函数,会按顺序先执行(函数参数)

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}
func defer6()  {
    a := 1
    b := 2
    // calc 充当了函数中的函数参数。即使在 defer 的函数中,它作为函数参数,定义的时候也会首先调用函数进行求值
    // 按照正常的顺序,calc("10", a, b) 首先被调用求值。calc("122", a, b) 排第二被调用
    defer calc("1", a, calc("10", a, b))
    defer calc("12",a, calc("122", a, b))
}
// 控制台输出:
/**
10 1 2 3   // 第一个函数参数
122 1 2 3  // 第二个函数参数
12 1 3 4   // 倒数第一个 calc
1 1 3 4    // 倒数第二个 calc
*/

defer 不影响 return的值

下面两个例子的结论是:

  • 无论 defer 内部调用传递的是值还是引用。都不会改变 return 的返回结果。返回值的确定,比 defer 早
func defer7() int {
    a := 2
    defer func() {
        a = a + 2
    }()
    return a
}
// 控制台输出:2
func add(i *int)  {
    *i = *i + 2
}

func defer8() int {
    a := 2
    defer add(&a)
    return a
}
// 控制台输出:2

原理:

    例如:return a,此行代码经过编译后,会被拆分为:
    1. 返回值 = a
    2. 调用 defer 函数
    3. return

闭包函数对 defer 的影响

函数中,值传递引用传递它们的区别是比较简单的,为基础的 C 语言指针知识。

而对于为什么 defer 修饰的背包函数,如果函数内部不是使用传参的参数时,它所能起到的引用修改作用。原理如下:

a := 2
func() {
    fmt.Println(a)
}()
a = a + 3
// 内存
闭包外:
    1. a 实例化
    2. a地址 ---> 2
闭包内:
    1. a 地址被传递进来
    2. a地址 ---> 2
    3. a = a + 3
    4. 输出 5

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅谈 php 采用curl 函数库获取网页 cookie 和 带着cookie去访问 网页的方法!!!!

    由于近段时间帮朋友开发一个能够查询正方教务系统的微信公众平台号。有所收获。这里总结下个人经验。 开讲前,先吐槽一下新浪云服务器,一个程序里的   同一个函数  ...

    林冠宏-指尖下的幽灵
  • C++ 制作 json 数据 并 传送给服务端(Server) 的 php

    json数据格式,这里举个基础的例子:       {"name":"LGH"} 在C++里面,我用个函数把特定的数据组合成 json 1 void toJs...

    林冠宏-指尖下的幽灵
  • 通俗易懂,各常用线程池的执行 流程图

    corePoolSize,maximumPoolSize,workQueue之间关系。

    林冠宏-指尖下的幽灵
  • 深入 Go 语言 defer 实现原理

    在上面的例子中,使用 for 循环将字符串 Naveen进行遍历后调用 defer,这些 defer调用仿佛就像被压栈一样,最后被推入堆栈的defer调用将被拉...

    luozhiyun
  • 探究 Go 语言 defer 语句的三种机制

    Golang 的 1.13 版本 与 1.14 版本对 defer 进行了两次优化,使得 defer 的性能开销在大部分场景下都得到大幅降低,其中到底经历了什么...

    张凯强
  • 2020-11-19:go中,defer原理是什么?

    福大大架构师每日一题
  • Go defer 会有性能损耗,尽量不要用?

    从结果上来,使用 defer 后的函数开销确实比没使用高了不少,这损耗用到哪里去了呢?

    sunsky
  • 深入理解defer(上)defer基础

    f() 函数首先通过调用 getResource() 获取了某种资源(比如打开文件,加锁等),然后进行了一些我们不太关心的操作,但这些操作可能会导致 f() ...

    阿波张
  • golang之流程控制(注意点)

    超蛋lhy
  • Go defer学习笔记

    A "defer" statement invokes a function whose execution is deferred to the moment...

    smartfly

扫码关注云+社区

领取腾讯云代金券