引入

函数是独立的,方法是绑定在类型上的。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 函数和工厂模式来初始化对象。