引入
函数是独立的,方法是绑定在类型上的。Go 没有类,但可以给任意类型(主要是结构体)定义方法,让数据和行为关联起来。方法通过接收者声明,值接收者操作副本,指针接收者操作原始值,选择哪种取决于是否需要修改数据。
正文
定义
定义
方法是带有接收者的函数,绑定在特定类型上。接收者可以是值接收者(操作副本)或指针接收者(操作原始值)。方法集决定了一个类型能实现哪些接口。
语法
// 值接收者
func (接收者变量 类型名) 方法名(参数) 返回值 {
// 方法体
}
// 指针接收者
func (接收者变量 *类型名) 方法名(参数) 返回值 {
// 方法体
}例子
值接收者
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 50
}值接收者拿到的是副本,方法内修改 r 不影响原始值。
指针接收者
type Counter struct {
count int
}
func (c *Counter) Increment() {
c.count++
}
func main() {
c := Counter{}
c.Increment()
c.Increment()
fmt.Println(c.count) // 2
}指针接收者操作原始值,方法内修改会反映到调用者。
值接收者不能修改原始值
type Person struct {
Name string
}
func (p Person) SetName(name string) {
p.Name = name // 修改的是副本,不影响原始值
}
func main() {
p := Person{Name: "Alice"}
p.SetName("Bob")
fmt.Println(p.Name) // Alice(没变)
}想让方法修改数据,必须用指针接收者。
调用方式统一
type Point struct {
X, Y int
}
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}
func (p *Point) Scale(factor int) {
p.X *= factor
p.Y *= factor
}
func main() {
p := Point{X: 3, Y: 4}
fmt.Println(p.String()) // (3, 4)
p.Scale(2)
fmt.Println(p.String()) // (6, 8)
}不管值接收者还是指针接收者,值和指针都能调用,Go 自动转换。
方法集
type File struct {
Name string
}
// 值接收者方法
func (f File) GetName() string {
return f.Name
}
// 指针接收者方法
func (f *File) SetName(name string) {
f.Name = name
}File 类型的方法集:{GetName}
*File 类型的方法集:{GetName, SetName}
指针类型的方法集包含值接收者的方法,反过来不包含。
给非结构体类型定义方法
type MyString string
func (s MyString) Upper() string {
return strings.ToUpper(string(s))
}
func main() {
s := MyString("hello")
fmt.Println(s.Upper()) // HELLO
}可以给任何自定义类型定义方法,但不能给其他包的类型或内置类型直接定义。
方法内调用其他方法
type Order struct {
Price float64
Quantity int
}
func (o Order) Total() float64 {
return o.Price * float64(o.Quantity)
}
func (o Order) TotalWithTax() float64 {
return o.Total() * 1.1
}方法之间可以互相调用,和普通函数一样。
常见写法
| 写法 | 说明 |
|---|---|
func (r Rect) Area() float64 | 值接收者,不修改数据 |
func (c *Counter) Add(n int) | 指针接收者,修改数据 |
p.Method() | 值或指针都能调用 |
(*p).Method() | 显式解引用调用(一般不需要) |
func (s MyType) String() string | 实现 Stringer 接口 |
特点
- 方法就是带接收者的函数,绑定在特定类型上
- 值接收者操作副本,不能修改原始数据
- 指针接收者操作原始值,可以修改数据
- 一般规则:需要修改用指针接收者,不需要修改用值接收者
- 同一个类型的方法建议统一用同种接收者,不要混用
- 指针类型的方法集包含值接收者的方法,反过来不行
- 只能给当前包定义的类型添加方法
- 值和指针调用方法时 Go 自动转换,不用手动
&或*
理解
方法是 Go 实现面向对象的核心方式。没有类,但通过结构体 + 方法实现了同样的效果。选值接收者还是指针接收者很简单:要不要改数据?要改就用指针,不改就用值。方法集的概念在实现接口时很重要,指针类型自动拥有值接收者的方法,反过来不行。
引出
方法是给类型绑定行为,那创建一个类型的实例时有没有固定的写法?接下来看 Go 构造函数模式,学习 Go 里怎么用 New 函数和工厂模式来初始化对象。