golang中的值传递与引用传递,如何理解结构体的方法?
先从一个例子说起
type Counter struct {count int
}func (c Counter) Inc() {c.count++
}func test1() {c := Counter{}do := func() {for i := 0; i < 10; i++ {c.count++}fmt.Println("done")}go do()go do()time.Sleep(3 * time.Second)fmt.Println(c.count)
}func test2() {c := Counter{}do := func() {for i := 0; i < 10; i++ {c.Inc()}fmt.Println("done")}go do()go do()time.Sleep(3 * time.Second)fmt.Println(c.count)
}
结果:test1() 打印20,test2() 打印0
现在焦点放在 test2() 上。
有人说,Inc() 方法的定义应该使用结构体指针,是的,答案的确如此,但是这底层的原因是什么呢?
对于普通的函数,我们很容易就知道是否需要传入指针,比如
func f1(obj *Counter)
func f2(obj Counter)
实际上,我们将结构体的方法做个反射就会发现
type Counter struct {count int
}func (c Counter) Inc() {c.count++
}func test3() {c := Counter{}reflectValue := reflect.TypeOf(c)fmt.Println(reflectValue.Method(0).Type)
}
打印:func(main.Counter)
或者
type Counter struct {count int
}func (c *Counter) Inc() {c.count++
}func test3() {c := &Counter{}reflectValue := reflect.TypeOf(c)fmt.Println(reflectValue.Method(0).Type)
}
打印:func(*main.Counter)
所以在调用结构体的方法时,c.Inc()
,实际上是Inc(c)
,这样一来,我们就很容易知道该使用Counter
还是*Counter
来定义Inc()
。
还是最开始的代码,没有做修改,我们在Inc中打印c的地址
func (c Counter) Inc() {fmt.Printf("%p\n", &c)c.count++
}
执行test2,打印如下
0xc0000a6058
0xc0000a60a0
0xc0000a60a8
0xc0000a60b0
0xc0000a60b8
0xc0000a60c0
0xc0000a60c8
0xc0000a60d0
0xc0000a60d8
0xc0000a60e0
0xc00001c050
done
0xc000102000
0xc0000a60e8
0xc0000a6100
0xc0000a6108
0xc0000a6110
0xc0000a6118
0xc0000a6120
0xc0000a6128
0xc0000a6130
done
0
可见每一次执行Inc,c的地址都不一样,这是因为do函数中的c只是外面的c的一个拷贝。
修改后的代码
func (c *Counter) Inc() {fmt.Printf("%p\n", c)c.count++
}
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
0xc0000a6058
done
done
20
将 test2() 中的c := &Counter{}
改成c := &Counter{}
,打印信息依旧正确,这是因为golang会根据Inc的定义,自动决定传入Counter
或者*Counter
进去。
如果将count int
换成map
或者slice
就是另外的情况了,因为这两个类型本身就是引用类型,你不需要显示的声明*
,它们就是以引用来工作的。
当函数参数比较大时,使用指针类型可以避免数据拷贝,提升效率。但是使用指针需要注意连贯性,也就是说后续都应该使用指针,否则这个变量就是混乱的。
在Go的 FAQ 中也有关于这一话题的阐述。
参考
Beware of copying mutexes in Go
Should I define methods on values or pointers?