golang get url传参_Golang之defer

defer 关键字是 Golang 引入的特征关键字之一,方便开发者做开发,在某些场景真的是太方便了,可以少写很多重复的代码。比如在错误处理场景,如果有多次返回,资源清理工作可能就是重复的。

常规方式:

func FileProcess() (msg string, err error) {  f, err := os.Open("demo-file-name")  if err != nil {    log.Fatalf("open file error, err: %#v", err)    return  }  fi, err := f.Stat()  // 获取 文件信息失败  if err != nil {    log.Fatalf("get file stat fail, err: %#v", err)    f.Close()    return  }  // 如果长度小于 200 不处理  if fi.Size() < 200 {    log.Fatalf("file size less than 200 bytes")    f.Close()        return  }  // Do something to deal with file content  // ....  return}

使用 defer:

func FileProcess() (msg string, err error) {  f, err := os.Open("demo-file-name")  if err != nil {    log.Fatalf("open file error, err: %#v", err)    return  }  defer f.Close()  fi, err := f.Stat()  // 获取 文件信息失败  if err != nil {    log.Fatalf("get file stat fail, err: %#v", err)    return  }  // 如果长度小于 200 不处理  if fi.Size() < 200 {    log.Fatalf("file size less than 200 bytes")    return  }  // Do something to deal with file content  // ....    return}
比较以上两种实现,使用 defer 的编码可以减少多次出现的 Close 方法,减少代码量,而且写出的代码更美观。说了这么多,来讲一下 defer 的作用和常见的使用场景。 1. defer 关键字作用:defer 关键字之后出现的表达式不会被立即执行,而是推迟到函数或者方法返回时执行,defer 会把执行的表达式和参数压入栈,等到外层函数返回时(return, 函数结束或者 panic)再把栈中的表达式弹出依次执行,遵循 LIFO 的顺序。如
func MyFunc() {  defer statement1()  defer statement2()  defer statement3()  return}
这段代码的 defer 入栈顺序是 statement1, statement2, statement3, 函数结束时的执行顺序是 statement3,statement2,statement1。

2. 使用场景。

2.1 清理资源。关闭打开的文件,断开网络连接等。

f, err := os.Open("demo-file-name")  if err != nil {    log.Fatalf("open file error, err: %#v", err)    return  }defer f.Close()

2.2 错误处理。处理 panic(注意,recover 必须在闭包中处理才有效)或者统一处理其他的错误。

func RecoverPanic() {  defer func() {    if err := recover(); err != nil {      fmt.Println("recover from panic")    }    fmt.Println("RecoverPanic exit")  }()  fmt.Println("I will panic")  panic("trigger recover")}

2.3 在返回时的业务规则统一处理。如 Restful 接口中, 可以使用 defer 处理最后的返回值赋值, 函数执行的 trace。

func TraceFunc() {  trace.WithRegion(context.Background(), "TraceFunc", func() {    fmt.Println("TraceFunc end")  })  time.Sleep(time.Second * 5)}

3. defer 的执行时机。

defer 真正执行的时机是在 当前函数或者方法执行结束之后,但是执行权真正返还给调用者之前。
func return  |defer  |Caller

defer 的坑

1. defer 语句执行时会把参数创建副本入栈(值传递)。

func DeferFunc() {  var a int = 2  defer fmt.Printf("defer1 a: %d \n", a)  a++  defer fmt.Printf("defer2 a: %d \n", a)  a++}
执行结果不是下面这样
defer1 a: 4defer2 a: 4
Golang 的传参方式只有值传递,在执行表达式 `fmt.Printf("defer1 a: %d \n", a) ` 就把 变量 a 创建了 1 份拷贝压入栈中, 所以等到真正执行时取出的数据就是 2。

真正的执行结果如下:

1aa30a41db0abd16d745513c988d910a.png

2. 返回值处理。

在具名返回函数或者方法中,可以借助于 defer 在闭包中修改返回值。非具名返回值和非闭包函数均不能实现这一点, golang 中 return 之后出现的变量与其他函数变量完全一样,可以被闭包访问、修改。
func NamedReturnFunc() (val1 int32, val2 int64) {  defer func() {    val1 = 5  }()  val1 = 99  val2 = time.Now().Unix()  fmt.Printf("NamedReturnFunc will return, val1: %d, val2: %d \n", val1, val2)return}

执行结果:

eb3aad13527e16e1a8f2d96053f3757a.png

如果上面的代码改成下面这样,执行结果就会完全不同。

func NonNamedReturnFunc() (int32, int64) {  var (    val1 int32    val2 int64  )    defer func() {    val1 = 5  }()  val1 = 99  val2 = time.Now().Unix()  fmt.Printf("NonNamedReturnFunc will return, val1: %d, val2: %d \n", val1, val2)  return val1, val2}

执行结果

78ce5406b7f4168a937f3fa72748c07b.png

3. defer 调用方法时,receiver 如果为 nil, 不会直接 panic, 一直到执行到才会 panic。

type MyType struct {  Name string}func (t *MyType) Hello() {  fmt.Printf("hello %s", t.Name)}func deferMethod() {  var m *MyType  defer m.Hello()  fmt.Println("I will return")}
执行结果

4a29cfa3d24bcf3ae99035d0c4d861ce.png

今天介绍的 golang 的 defer 关键字使用场景和常见的坑, 你都 get 到了吗?
  1. defer 用于推迟表达式的执行时机到当前函数结束。

  2. defer 最常见的使用场景是 资源清理,错误处理(panic 恢复), 函数执行时间统计, 业务场景统一处理。

  3. defer 在调用非闭包表达式时会把参数压入栈中, 之后的修改都不会影响到已经入栈的参数。

  4. defer 后面如果调用方法,如果 receiver 是 nil,一直到函数终止后真正执行表达式时才会 panic。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值