Golang中的defer(2)

简介: Golang中的defer(2)

总结一下


即 如果传参进defer后面的函数(无论是闭包(i)方式还是字方法f(i)方式,或是直接跟如fmt.Println(i)),defer回溯时均以以当时传参时i的值去计算

反之,defer回溯时,以最后i的值带入计算;(参考下面的例子).

参考:

Go面试题答案与解析




几种写法之间的归类与区别

package main
import "fmt"
func main() {
  rs := foo6()
  fmt.Println("in main func:", rs)
}
func foo6() int {
  i := 0
  defer fmt.Println("in defer :", i)
  //defer func() {
  //  fmt.Println("in defer :", i)
  //}()
  i = 1000
  fmt.Println("in foo:", i)
  return i+24
}

输出为:

in foo: 1000
in defer : 0
in main func: 1024

如果改为:

package main
import "fmt"
func main() {
  rs := foo6()
  fmt.Println("in main func:", rs)
}
func foo6() int {
  i := 0
  //defer fmt.Println("in defer :", i)
  defer func() {
    fmt.Println("in defer :", i)
  }()
  i = 1000
  fmt.Println("in foo:", i)
  return i+24
}

输出为:

in foo: 1000
in defer : 1000
in main func: 1024

也可见,

defer fmt.Println("in defer :", i)

相当于

defer func(k int) {
    fmt.Println(k)
    }(i)

func f(k int){
  fmt.Println(k)
}

这时的参数,都是传递时的值

而如

  defer func() {
    fmt.Println("in defer :", i)
    }()

这时的参数,为最后return之前那一刻的值




defer会影响返回值吗?


函数的return value 不是原子操作, 在编译器中实际会被分解为两部分:返回值赋值return 。而defer刚好被插入到末尾的return前执行(即defer介于二者之间)。故可以在defer函数中修改返回值

package main
import (
  "fmt"
)
func main() {
  fmt.Println(doubleScore(0))    //0
  fmt.Println(doubleScore(20.0)) //40
  fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
  defer func() {
    if rs < 1 || rs >= 100 {
      //将影响返回值
      rs = source
    }
  }()
  rs = source * 2
  return
  //或者
  //return source * 2
}

输出为:

0
40
50

再如:

func main() {
    fmt.Println("foo return :", foo2())
}
func foo() map[string]string {
    m := map[string]string{}
    defer func() {
        m["a"] = "b"
    }()
    return m
}

输出为:

foo return : map[a:b]

又如:

package main
import "fmt"
func main() {
  fmt.Println("foo return :", foo())
}
func foo() int {
  i := 0
  defer func() {
    i = 10086
  }()
  return i + 5
}

输出为:

foo return : 5

若作如下修改:

func foo() (i int) {
  i = 0
  defer func() {
    i = 10086
  }()
  return i + 5
}

则返回为:

foo return : 10086

return之后的语句先执行,defer后的语句后执行


return value拆解为两步: 确定value值,然后return..即如果return 后面是个方法或者复杂表达式,且有某个值i,会先计算.完成后defer再执行,如果defer里面也有对i的改动,是可以影响返回值的

(给函数返回值申明变量名, 这时, 变量的内存空间空间是在函数执行前就开辟出来的,且该变量的作用域为整个函数,return时只是返回这个变量的内存空间的内容,因此defer能够改变返回值)

defer不影响返回值,除非是map、slice和chan这三种引用类型,或者返回值定义了变量名




参考:

Golang研学:如何掌握并用好defer--存疑("引用传递"那里明显错误)

Golang中的Defer必掌握的7知识点


关于 值传递和引用传递:


Go语言,C语言,都是值拷贝语言,当返回一个变量时,如return i,返回值是没有名称的(匿名的),不是把i返回给上层(方法/函数),而是把i的值返回给上层的一个内存.

(上层调用方(如main)得到的这个变量(即子方法的返回值)的内存块,和子方法的这个i的内存,不是一个...所以经过值拷贝后,main里得到的值不是i本身.)

当return时,上层会拷贝一份i,存在内存块里(如记为变量p),main里就得到了p里的值..然后子方法触发defer,只会修改局部变量i,而不会影响p, 进而也不会影响main里得到的值

而如果指定了返回值的名称如t,则上层存储变量值的内存p,始终会与t的值一致.进而就可以修改

而map/slice/chan为引用类型,即返回的是一个指向内存地址的指针,故而:复制了一份返回值的地址---触发了defer,修改了返回值---main中根据返回值地址得到的值,也会发生改变

特别感谢刘丹冰Aceld大神的不吝指点与赐教




更多关于"值传递和引用传递",可参考:

Java 到底是值传递还是引用传递?

C++ 值传递、指针传递、引用传递详解

值传递:

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,

不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

指针传递:

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

引用传递:

形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈

中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过

栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。


更多扩展:

使用 defer 还是不使用 defer?

最新版本对此做了优化


目录
相关文章
|
4天前
|
Serverless Go
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
|
5天前
|
人工智能 数据库连接 Go
golang defer 详解
golang defer 详解
14 0
|
3月前
|
Go 开发者
Golang深入浅出之-Go语言 defer、panic、recover:异常处理机制
Go语言中的`defer`、`panic`和`recover`提供了一套独特的异常处理方式。`defer`用于延迟函数调用,在返回前执行,常用于资源释放。它遵循后进先出原则。`panic`触发运行时错误,中断函数执行,直到遇到`recover`或程序结束。`recover`在`defer`中捕获`panic`,恢复程序执行。注意避免滥用`defer`影响性能,不应对可处理错误随意使用`panic`,且`recover`不能跨goroutine捕获panic。理解并恰当使用这些机制能提高代码健壮性和稳定性。
60 2
|
10月前
|
安全 Go
Golang 语言中的 defer 怎么使用?
Golang 语言中的 defer 怎么使用?
38 0
|
3月前
|
Java Go
浅谈Golang 不同版本的defer
浅谈Golang 不同版本的defer
42 1
|
3月前
|
存储 编译器 测试技术
Golang底层原理剖析之defer
Golang底层原理剖析之defer
41 0
|
编译器 Go
Golang中的defer
Golang中的defer
56 0
|
Go
Golang中的defer(1)
Golang中的defer(1)
67 0
|
Go 数据库 C#
延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17
先行定义,延后执行。不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finally语法块,函数结束时强制执行的代码逻辑,但是defer在语法结构上更加优雅,在函数退出前统一执行,可以随时增加defer语句,多用于系统资源的释放以及相关善后工作。当然了,这种流程结构是必须的,形式上可以不同,但底层原理是类似的,Golang 选择了更简约的defer,避免多级嵌套的try except finally 结构。
延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17