Go语言中的方法是一种特殊的函数,它和某个特定的类型进行了绑定,调用方法时需要通过该类型的实例来触发,这种绑定关系就是通过接收者来实现的。接收者出现在func关键字和方法名之间,明确了这个方法属于哪个类型。
方法的基本定义形式
Go语言方法的定义语法和普通函数类似,区别在于func关键字后需要添加接收者声明,格式如下:
// 值接收者方法定义
func (接收者变量 接收者类型) 方法名(参数列表) 返回值列表 {
方法体
}
// 指针接收者方法定义
func (接收者变量 *接收者类型) 方法名(参数列表) 返回值列表 {
方法体
}
接收者变量是接收者在方法内部的名称,可以自定义,通常取接收者类型首字母的小写形式,接收者类型可以是自定义的结构体、基本类型等。
值接收者与类型的关联
当接收者是值类型时,这个方法会和该类型的值实例绑定,调用方法时会复制一份接收者的副本传入方法内部,方法内部对接收者的修改不会影响原实例。
package main
import "fmt"
// 定义自定义类型Person
type Person struct {
Name string
Age int
}
// 值接收者方法,修改年龄
func (p Person) SetAgeValue(newAge int) {
p.Age = newAge
fmt.Println("方法内修改后年龄:", p.Age)
}
func main() {
p := Person{Name: "张三", Age: 20}
fmt.Println("调用方法前年龄:", p.Age)
p.SetAgeValue(25)
fmt.Println("调用方法后年龄:", p.Age)
}
上述代码的执行结果是调用方法前年龄20,方法内修改后年龄25,调用方法后年龄还是20,说明值接收者方法不会修改原实例的数据。
指针接收者与类型的关联
当接收者是指针类型时,方法会和该类型的指针实例绑定,调用方法时会传入接收者的地址,方法内部对接收者的修改会直接作用到原实例上。
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 指针接收者方法,修改年龄
func (p *Person) SetAgePointer(newAge int) {
p.Age = newAge
fmt.Println("方法内修改后年龄:", p.Age)
}
func main() {
p := Person{Name: "张三", Age: 20}
fmt.Println("调用方法前年龄:", p.Age)
// Go会自动将值实例转换为指针调用指针接收者方法
p.SetAgePointer(25)
fmt.Println("调用方法后年龄:", p.Age)
}
上述代码执行结果是调用方法前年龄20,方法内修改后年龄25,调用方法后年龄变为25,说明指针接收者方法会修改原实例的数据。
接收者类型选择的核心原则
在实际开发中可以根据以下场景选择接收者类型:
- 如果方法需要修改接收者的数据,必须使用指针接收者
- 如果接收者是较大的结构体,为了避免值复制的性能开销,建议使用指针接收者
- 如果方法不需要修改接收者数据,且接收者是较小的类型,可以使用值接收者
- 同一个类型的不同方法,接收者类型要保持一致,要么都是值接收者,要么都是指针接收者
接收者与接口实现的关联
Go语言的接口实现是隐式的,只要某个类型实现了接口定义的所有方法,就认为该类型实现了这个接口。这里的实现判断也和接收者类型有关:
- 值类型实例可以调用值接收者方法和指针接收者方法
- 指针类型实例可以调用值接收者方法和指针接收者方法
- 如果接口的方法列表中包含指针接收者方法,那么只有该类型的指针实例能实现这个接口,值实例无法实现
package main
import "fmt"
// 定义接口
type Animal interface {
Speak()
}
type Dog struct {
Name string
}
// 指针接收者实现Speak方法
func (d *Dog) Speak() {
fmt.Println("汪汪汪")
}
func main() {
var a Animal
// d1是值实例,无法赋值给Animal接口
// d1 := Dog{Name: "小黑"}
// a = d1 // 这行会编译报错
d2 := &Dog{Name: "小黑"}
a = d2 // 指针实例可以赋值
a.Speak()
}
通过上述规则可以看出,接收者不仅决定了方法和类型的绑定关系,还会影响类型的接口实现逻辑,是Go语言方法体系中非常核心的部分。