环境
Windows 10
golang 1.17
代码
package mainimport "fmt"type Person struct {age int
}// 接收者是值类型的方法
func (p Person) valueAddAge() {p.age++
}// 接收者是指针类型的方法
func (p *Person) ptrAddAge() {p.age++
}func main() {// p1是值类型,调用接收者是值类型的方法p1 := Person{age: 18}p1.valueAddAge()fmt.Println(p1.age) // 输出18,没有改变// p2是值类型,调用接收者是指针类型的方法p2 := Person{age: 18}p2.ptrAddAge()fmt.Println(p2.age) // 输出19,有改变// p3是指针类型,调用接收者是值类型的方法p3 := &Person{age: 18}p3.valueAddAge()fmt.Println(p3.age) // 输出18,没有改变// p4是指针类型,调用接收者是指针类型的方法p4 := &Person{age: 18}p4.ptrAddAge()fmt.Println(p4.age) // 输出19,有改变
}
1.调用方法时,都是把调用者(调用者是指上面代码中的p1
、p2
、p3
、p4
变量)复制一份传递给方法。
2.如果调用者的类型,和接收者的类型不相同,会隐式的把调用者类型转换成接收者的类型。
例如上述代码中的p2
和p3
就属于类型不一致的。p2.ptrAddAge()
会转换成(&p2).ptrAddAge()
,对p2
取地址,就得到了一个指针,这样调用者的类型就和接收者一致了,然后把指针复制一份传给方法;
同理,p3.valueAddAge()
会转换成(*p3).valueAddAge()
,对p3
取值,得到了一个值类型的变量,再把变量复制一份传给方法。
如果调用者是指针类型,我们知道,即使将指针复制一份,它指向的原始数据还是不变的,因此在方法中对调用者的数据进行修改,是会影响到调用者的;
但是如果调用者是值类型的,值类型的变量被复制了一份,那新的变量就不是原来的那个变量了,因此在方法里对新变量做修改,是不会影响到原调用者的。
结论
值接收者和指针接收者的区别,就是看方法里的操作是否会影响到调用者。值接收者不会影响,指针接收者会影响
其它:与interface接口组合使用
在上一个场景中,无论调用者
和接收者
是什么类型,调用者都能成功调用到方法。但是,当与 interface
接口组合使用时,情况又会变得不一样,比如以下代码:
package mainimport "fmt"// Human接口
type Human interface {getAge() int
}// Person结构体
type Person struct {age int
}// 实现Human接口的getAge方法,接收者是指针类型
func (rec *Person) getAge() int {return rec.age
}func main() {// 创建一个Person对象,值类型p1 := Person{age: 18}// 因为Person实现了Human接口,所以我们可以把它赋值给一个类型为Human的接口变量var ivar Human = p1 // 这一行会报编译错误// 通过接口变量调用接口方法age := ivar.getAge()fmt.Println(age)
}
上述代码定义了一个Human
接口,并且在Person
结构体中实现了这个接口的getAge
方法(接收者为指针类型)。
然后创建一个结构体对象p1
,类型是值类型。将p1
赋值给接口类型变量ivar
,通过ivar
调用getAge
方法,但此时编译器会报错,因为p1
是值类型,方法接收者是指针类型,两者类型不一致。
当我们通过接口变量(上述中的ivar
)调用方法的时候,需要注意以下规则:
- 接收者是值的方法,可以通过值调用
- 接收者是值的方法,可以通过指针调用,因为指针会首先被解引用
- 接收者是指针的方法,可以通过指针调用
- 接收者是指针的方法,不可以通过值调用,因为存储在接口中的值没有地址
上述我们的代码就属于第4种情况,因此编译失败。
将一个值赋值给一个接口类型变量时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
参考
- https://blog.csdn.net/u010853261/article/details/100941972