概念
什么是方法?什么是函数?
Golang 中同时有函数和方法,方法是一个包含了接收者(receiver)的函数,receiver 可以是内置类型或者自定义类型struct的一个值或者是一个指针;函数是指不属于任何结构体、类型的方法,也就是说函数是没有接收者的。(可以将方法的receiver看做是函数的第一个argument)
// 函数
func main() {
sum := add(1, 2)
fmt.Println(sum)
}
func add(a, b int) int {
return a + b
}
// 方法
type person struct {
name string
}
func (p person) String() string{
return "the person name is "+p.name
}
什么是值方法和指针方法?
- 值方法:方法的接收者是值类型
- 指针方法:方法的接收类型是指针类型
type T struct {
Name string
}
// 值方法,方法接收者为值类型
func (t T) M1() {
t.Name = "name1"
}
// 指针方法,方法接收者为指针类型
func (t *T) M2() {
t.Name = "name2"
}
区别
这两种方法类型有什么区别吗?
- 值方法(value methods)可以通过指针和值调用,
- 指针方法(pointer methods)只能通过指针来调用,但是如果某个值是可寻址的(addressable,或者说左值),那么编译器会在值调用指针方法时自动插入取地址符,使得在此情形下看起来像指针方法也可以通过值来调用
如果方法接收者为对象的指针,则会修改原对象,如果方法接收者为对象的值,那么在方法中被操作的是原对象的副本,不会影响原对象
package main
import "fmt"
func main() {
t1 := T{"t1"}
fmt.Println("M1调用前:", t1.Name)
t1.M1()
fmt.Println("M1调用后:", t1.Name)
fmt.Println("M2调用前:", t1.Name)
t1.M2()
fmt.Println("M2调用后:", t1.Name)
}
type T struct {
Name string
}
// 方法实参和形参都是值类型,传递的是值的副本,所以不会对t1造成影响
func (t T) M1() {
t.Name = "name1"
}
// 形参是指针类型,需要对实参进行自动取引用,传递的是t1的指针副本,所以修改后会对t1造成影响
func (t *T) M2() {
t.Name = "name2"
}
// 结果
M1调用前: t1
M1调用后: t1
M2调用前: t1
M2调用后: name2
package main
import "fmt"
func main() {
t1 := &T{"t1"}
fmt.Println("M1调用前:", t1.Name)
t1.M1()
fmt.Println("M1调用后:", t1.Name)
fmt.Println("M2调用前:", t1.Name)
t1.M2()
fmt.Println("M2调用后:", t1.Name)
}
type T struct {
Name string
}
// 实参是指针类型,会自动解引用
func (t T) M1() {
t.Name = "name1"
}
// 实参和形参都是指针类型
func (t *T) M2() {
t.Name = "name2"
}
// 结果
M1调用前: t1
M1调用后: t1
M2调用前: t1
M2调用后: name2
取舍
值方法和指针方法两者如何取舍其实和另一个问题等价:就是你在定义函数时如何传参——是传值副本还是传指针副本
- 如果方法需要修改receiver本身,那无疑需要传指针副本,也就是使用指针方法
- 当值比较大的时候,值拷贝代价会变大,这时候也该考虑使用指针方法
- 不可变对象等右值不能使用指针方法
扩展
go语言中的左值和右值
| 值类型 | 区别 | 情形 |
|---|---|---|
| 左值 | 可寻址,可以通过&取地址符,获取内存地址 | |
| 右值 | 不可寻址,没有分配内存地址 | 1.常量在编译期确定不会分配内存 2.函数返回值不分配内存 3.切片 |
关于go编译器自动解引用
- 用指针类型的实参调用形参为值类型的方法(会进行“自动解引用”)
- 用值类型的实参调用形参为指针类型的方法(会进行“自动取引用”)
总结
- 当传递给方法的形参是一个值类型时(值类型接收器),原值的内容不会受到影响,因为值传递,传递进来的是原值的复制副本(形参是副本);相反,当方法的形参是一个指针类型时,原值的内容将可能受到影响,因为传递的是指针类型副本,虽然是副本但是指针中存储的地址仍然是实参的真实地址。
- 方法接收者类型总结
- 方法接收者是值,对象的值和指针均可以调用该方法;
- 方法接收者是指针,只能通过指针或
addressable value调用; - 当方法的接收者是值时,不管是值调用还是指针调用,方法内部都是对原对象的副本进行操作,不会影响原对象;
- 当方法的接收者是指针时,不管是值调用还是指针调用,方法内部都是通过指针对原对象进行操作,会影响原对象。