golang中defer延迟机制的实现示例

作者:

文章目录
  • defer是go中一种延迟调用机制。 执行时机 defer后面的函数只有在当前函数执行完毕后才能执行。 执行顺序 将延迟的语句按defer的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,通常用于释放资源。 多个defer本质就是用栈存储,先进后出。
  • //最后不要忘记函数调用 //使用匿名函数 defer func (形参列表){ }(实参) //最后不要忘记调用函数 //使用事先声明的函数 defer 函数名(实参) //最后不要忘记调用方法 defer 方法名(形参列表){ }(实参) defer的功能一般是用于释放资源。 defer后面的函数是可以有返回值的,但是一般没有作用。
  • 多个defer出现的时候,它会把defer之后的函数压入一个栈中延迟执行,也就是先进后出(LIFO). 写在前面的defer会比写在后面的defer调用的晚。下面通过一个示例看一下: package defer_knowledge import “fmt” type Student struct{ Name string } func (s Student) GetName(n int){ fmt.Printf(“这是第%d个defern”,n) } func sayHello(n int){ fmt.Printf(“这是第%d个defern”,n) } //验证defer的执行顺序 func DeferFirst(){ fmt.Println(“hello world”) var age int = 25 defer func (){ fmt.Println(“我是第1个defer”) }() age++ /* 虽然defer的执行时机在return之后 但是声明defer时,结构体实例要先声明,否则无法访问结构体实例方法 */ var student Student defer student.GetName(2) defer func (){ fmt.Println(“我是第3个defer”) }() defer sayHello(4) } 结果 这是第4个defer我是第3个defer这是第2个defer我是第1个defer 图示
  • 图示 可以看到 return 执行的时候,并不是原子性操作,一般是分为两步:将结果x赋值给了返回值,然后执行了RET指令;而defer语句执行的时候,是在赋值变量之后,在RET指令之前。所以这里注意一下。返回值和x的关系。如果x是一个值类型,这里是进行了拷贝的。 执行图示意
  • 目录
    • defer是什么
    • defer定义
      • 注意事项
    • 多个defer的执行顺序
      • 延迟参数传入时机
        • 基本语法
        • 小结
      • 声明时机和执行时机
        • 声明时机
        • 执行时机
      • defer与return的区别
        • 函数返回值
          • 不具名返回
          • 具名返回

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

      执行时机

      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("年龄为%dn",age)
      }(age)
      age := 15
      

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

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

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

      结果

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

      图示

      注意事项

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

      package defer_knowledge
      
      import "fmt"
      
      //defer的参数是声明时传入的
      func DeferParams(){
      	var age = 10
      	defer func(a int){
      		fmt.Printf("defer内的参数为%dn",a)
      	}(age)
      
      	age = 25
      
      	fmt.Printf("age已经变成了%dn",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内参数为%dn",*a)
      	}(&age)
      
      	defer func(a int){
      		fmt.Printf("defer内的参数为%dn",a)
      	}(age)
      
      	age = 25
      
      	fmt.Printf("age已经变成了%dn",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内的参数为%#vn",a)
      	}(arr)
      
      	arr[2] = 10
      	fmt.Printf("arr已经变成了%#vn",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为 11
      		age = age+5
      		fmt.Printf("age的值为%dn",age)
      	}()
      
      	defer func(){
      		//defer执行时机为函数结束后,所以此时访问到的 age = 10
      		age++
      		fmt.Printf("age的值为%dn",age)
      	}()
      
      	age = 10
      }
      

      结果

      age的值为11
      age的值为16

      图示

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

      执行图示意

      形式

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

      案例1

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

      解析

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

      误区1

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

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

      原因:

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

      正确思维

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

      结果

      num的值为16
      target的值为16

      原因

      func DeferAndReturn1() *int{
          var num int
          var ptr = new(int)
          ptr = &num
          defer func(){
              num++
              //num的值为16
              fmt.Printf("num的值为%dn",num)
          }()
          num = 15
          /*
          	实际操作
          	copy_ptr = ptr
          	由于ptr是引用类型,所以defer对ptr的影响会影响到copy_ptr
          	
          	num的本质就是*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 = 10
      	return
      }
      
      num2 := DeferAndReturn2()
      fmt.Printf("外部num的值为%dn",num2)
      

      结果

      外部num的值为11

      原因

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

      到此这篇关于golang中defer延迟机制的实现示例的文章就介绍到这了,更多相关golang defer延迟机制内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 

      您可能感兴趣的文章:

      • Golang defer延迟语句的实现
      • Golang之defer 延迟调用操作
      • Golang学习笔记之延迟函数(defer)的使用小结

      站内搜索