zoukankan      html  css  js  c++  java
  • 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
    

  • 相关阅读:
    Objective-C系列总结之基础知识
    OC自动释放池autoreleasepool介绍
    OC导入框架方式#import、@import的区别
    OC源文件扩展名
    安装好MySQL后就开始学习如何后台创建自己的数据库吧!
    如何修改Eclipse中的快捷键
    安装MySQL的详细步骤
    Eclipse运行错误:Failed to load the JNI shared library的解决办法
    导航栏返回带的数据
    flutter Container
  • 原文地址:https://www.cnblogs.com/linguanh/p/10587189.html
Copyright © 2011-2022 走看看