Go 新手笔记

2019/05/26 Go

Go 学习笔记

:= 和 = 的区别

// = 必须使用 var 来申明,例如:
var a
a=100
// 或者
var b = 100
// 或者
var c int = 100
 
// := 是声明并赋值,并且系统自动推断类型,不需要 var 关键字
d := 100

如何选择 T 和 *T

  1. 不想变量被修改。 如果你不想变量被函数和方法所修改,那么选择类型T。相反,如果想修改原始的变量,则选择*T
  2. 如果变量是一个大的 struct 或者数组,则副本的创建相对会影响性能,这个时候考虑使用*T,只创建新的指针,这个区别是巨大的
  3. (不针对函数参数,只针对本地变量/本地变量)对于函数作用域内的参数,如果定义成T,Go编译器尽量将对象分配到栈上,而*T很可能会分配到对象上,这对垃圾回收会有影响

defer使用时的坑

先来看看几个例子。

例1:
func f() (result int) {
   defer func() {
       result++
   }()
   return 0
}
例2:

func f() (r int) {
    t := 5
    defer func() {
      t = t + 5
    }()
    return t
}
例3:

func f() (r int) {
   defer func(r int) {
         r = r + 5
   }(r)
   return 1
}

请读者先不要运行代码,在心里跑一遍结果,然后去验证。

例1的正确答案不是0,例2的正确答案不是10,如果例3的正确答案不是6……

defer是在return之前执行的。这个在 官方文档中是明确说明了的。要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令!

函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。

其实使用defer时,用一个简单的转换规则改写一下,就不会迷糊了。改写规则是将return语句拆成两句写,return xxx会被改写成: 返回值 = xxx 调用defer函数 空的return 先看例1,它可以改写成这样:

func f() (result int) {
    result = 0  //return语句不是一条原子调用,return xxx其实是赋值+ret指令
    func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
        result++
    }()
    return
}

所以这个返回值是1。

再看例2,它可以改写成这样:

func f() (r int) {
    t := 5
    r = t //赋值指令
    func() {        //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
        t = t + 5
    }
    return        //空的return指令
}

所以这个的结果是5。 最后看例3,它改写后变成:

func f() (r int) {
    r = 1  //给返回值赋值
    func(r int) {        //这里改的r是传值传进去的r,不会改变要返回的那个r值
         r = r + 5
    }(r)
    return        //空的return
}

所以这个例子的结果是1。 defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 ret之间,因此可能有机会改变最终的返回值。

Search

    Table of Contents