讲解
方法就是「挂在某个类型上的函数」:func (c Circle) Area() float64 给 Circle 类型定义了 Area 方法,括号里的 c 叫接收者(receiver),类似其他语言的 this/self,但它是显式写出来的。调用时圆.Area() 的形式让数据和行为读起来浑然一体:c.Area() 就是「c 的面积」。
方法可以定义在任何「本包命名的类型」上——不能给 int、string 这类内置类型直接加方法,但 type MyInt int 定义自己的类型后就能挂方法,这是 Go 扩展标准行为的常用手法(比如让自定义类型实现 fmt.Stringer 接口)。
最重要的抉择是接收者用值还是指针:值接收者拿到副本,方法里修改不影响原对象;指针接收者才能修改。经验法则简单粗暴:方法要修改接收者、或结构体较大,就用指针接收者;并且同一个类型的方法集要保持一致——一旦有一个方法用了指针接收者,其余方法也都用指针,避免使用方困惑。调用层面 Go 会自动取地址或解引用,c.Inc() 和 (&c).Inc() 等价,语法上不用操心。
示例
为自定义类型定义方法:
package main
import (
"fmt"
"math"
)
type Circle struct {
R float64
}
type Rect struct {
W, H float64
}
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
fmt.Printf("圆面积: %.2f\n", Circle{R: 2}.Area())
fmt.Println("矩形面积:", Rect{W: 3, H: 4}.Area())
}
指针接收者修改状态,值接收者只读:
package main
import "fmt"
type Counter struct {
N int
}
func (c *Counter) Inc() { c.N++ } // 指针接收者:能修改
func (c Counter) Value() int { return c.N } // 值接收者:只读
func main() {
c := Counter{}
c.Inc() // 等价于 (&c).Inc(),编译器自动取地址
c.Inc()
fmt.Println("计数:", c.Value()) // 2
}
给自定义基础类型挂方法:
package main
import "fmt"
type Celsius float64 // 自定义的摄氏度类型
func (c Celsius) Fahrenheit() float64 { return float64(c)*9/5 + 32 }
func main() {
t := Celsius(25)
fmt.Printf("%.0f°C = %.0f°F\n", t, t.Fahrenheit())
}
常见坑
- 值接收者里修改字段然后发现没生效:值接收者改的是副本,方法返回后原对象纹丝不动。需要修改一律指针接收者,这是新手最高频的方法 bug。
- 同一类型混用两种接收者:Go 不禁止混用,但会导致「值类型的方法集不含指针方法」这类微妙问题(接口章节会碰到)。团队惯例:一个类型统一用一种,优先指针。
- 试图给内置类型加方法:func (s string) Shout() 编译不过,只能给本包定义的类型加方法。想扩展 string 行为就 type MyString string。
- nil 接收者 panic:指针接收者方法里直接用字段前,如果接收者可能为 nil 要先判断。部分类型(如标准库的一些)特意支持 nil 接收者并做了防御,自定义类型要自己兜底。
小结
方法是带接收者的函数,指针接收者改状态、值接收者只读,同一类型保持接收者一致。下一节学习 Go 多态的核心:接口。