本文已参与「新人创作礼活动,一起开启掘金创作之路。
背景
刚开始从PHP转Go的时候也接触到了指针类型,也了解一些区别但是到底有哪些区别是没有具体做过了解,也不知道区别都在哪些地方,今天正好有时间想起了这个很久的疑惑,就具体来说下我的认识,还是WWH规矩.
WHAT
1. 什么是值类型?
Go语言的值类型有以下几种:
· 基本数据类型(数值、布尔、字符、字节)
· 数组array
结构体struct
一个存储值类型的变量,它在函数或方法的参数传递中是属于拷贝传递的,即:传递的值类型参数在函数或方法内部修改不会影响外部的变量数据,而只会影响其拷贝的数据.
2.什么是指针类型? Go对指针进行精简设计,只允许对指针进行取值和取地址操作,而且其最多支持二级指针,这大大简化了程序员的使用难度,指针类型简单点可以理解为根据内存地址取值,上面我们提到值传递,在传递基本数据类型时,这种类型的变量只占1~8字节,可以说非常廉价,对性能影响可以忽略不计。但在传递数组和结构体时,如果数据体量较大,传递一个数组或结构体代价是比较大的。
在开发中会遇到这种需求:一个变量传递给一个函数或方法,希望函数内部对变量的修改可以影响外部,这时我们就需要把变量的内存地址作为参数传给函数或方法,这种传地址的方式就是传递指针类型或引用类型,由此说明指针的本质就是内存地址,一个指针类型的变量存放的就是指向源数据的内存地址,一个值类型的变量都可以通过指针操作符获取数据的内存地址,而存放内存地址的变量的类型就是指针类型
WHY
值接收者和指针接收者的区别?方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者, 给一个函数添加一个接收者,它就变成了方法。接收者可以是值接收者,也可以是指针接收者。在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法:指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收的类型
举个栗子
package main
import "fmt"
type Person struct {
age int
}
func (p Person) howOld() int {
return p.age
}
func (p *Person) GrowUp() {
p.age += 1
}
func main() {
//值类型
qcrao := Person{
age: 18,
}
//值类型调用值类型方法
fmt.Println("值类型方法打印", qcrao.howOld())
//值类型调用指针方法
qcrao.GrowUp()
fmt.Println("值类型调用指针方法打印", qcrao.howOld())
/************一下是指针类型***********/
//指针类型
stefno := &Person{
age: 50,
}
//调用值类型的方法
fmt.Println("指针调用值类型的方法", stefno.howOld())
//调用指针类型方法
stefno.GrowUp()
fmt.Println("指针调用指针类型的方法", stefno.howOld())
}
打印结果:
值类型方法打印 18
值类型调用指针方法打印 19
指针调用值类型的方法 50
指针调用指针类型的方法 51
实际上只要调用的GrowUp()方法,不论是指针调用还是值调用他的age都会改变,具体实现是在编译器中做的一些工作
| 值接收者 | 指针接收者 | |
|---|---|---|
| 值调用者 | 方法会使用调用者传值 | 会使用内存地址的值 |
| 指针调用者 | 指针被解引用的为值,上例中值引用调用的howOld()方法中实际上是调用的&stefno.howOld()方法 | 类似于指针的传值,复制一份指针,方法中的操作会影响调用者 |
再有就是实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法,是因为上面的结论背后有一个简单地解释: 接收者是指针类型的方法,很可能在方法中会对收者的属性进行更改操作,从而影响接收者:而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响.
所以,当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者;而当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型只会产生一个复制,不会真正影响调用者。
HOW
两者分 别在何时使用
如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者:如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。
使用指针作为方法的接收者的理由如下:
1)方法能够修改接收者指向的值。
2)避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。
但是决定使用值接收者还是指针接收者,不是由该方法是否修改了调用者( 也就是接收者),而是应该基于该类型的本质。
如果类型具备“原始的本质”,也就是说它的成员都是由Go语言里内置的原始类型,如字符 串、整型值等构成,那就定义值接收者类型的方法。像内置的引用类型,如slice、 map、 interface. channel, 这些类型比较特殊,声明它们的时候,实际上是创建了-一个header, 对于它们 也是直接定义值接收者类型的方法。这样,调用函数时,是直接复制了这些类型的header, 而header本身就是为复制设计的。这种也应该是声明值接收者类型的方法。
如果类型具备非原始的本质,不能被安全地复制,这种类型总是应该被共享,那就定义指针接 收者的方法。比如Go源码里的文件结构体(stmuet Fil)就不应该被复制,应该只有一份实体, 就要定义指针接收者类型的方法。
最后
如果实现了接收者是值类型的方法,会隐含的也实现了接收者是指针类型的方法