Files
cs-note/hzh/GolangStar/Go语言基础/Go语言方法.md
T

4.3 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
方法
2026-06-07 15:00

Go 语言方法

概述

方法是绑定在特定类型上的函数。Go 中任何类型(包括内置类型的别名)都可以有方法。本文讲解值接收者 vs 指针接收者的选择规则,以及通过嵌入实现"继承"。

正文

方法 vs 函数:本质区别是什么?

方法 = 函数 + 接收者(receiver)。接收者是该方法所绑定的类型实例。

type Point struct{ X, Y float64 }

// 方法:注意 func 和 方法名之间多了 (p Point)
func (p Point) Distance(q Point) float64 {
    dx := p.X - q.X
    dy := p.Y - q.Y
    return math.Sqrt(dx*dx + dy*dy)
}

p := Point{3, 4}
fmt.Println(p.Distance(Point{0, 0}))  // 5 — 调用方式像字段访问

[!note] 📝 "struct + 方法 ≈ class" Go 没有类(class),但 struct 提供数据,method 提供行为,组合起来就是面向对象的核心概念。

值接收者 vs 指针接收者

这是 Go 方法设计中最关键的选择:

值接收者 (t T) 指针接收者 (t *T)
传入什么 拷贝一份数据 传入地址
能修改原值吗 ❌ 不能 ✅ 能
调用方式 两者皆可 两者皆可(Go 自动解引用/取地址)
type Student struct{ Score int }

// 值接收者:读取
func (s Student) GetScore() int {
    return s.Score
}

// 指针接收者:修改
func (s *Student) SetScore(score int) {
    s.Score = score
}

[!tip] 💡 选择规则

  1. 需要修改原值 → 必须用指针接收者
  2. 结构体较大 → 优先用指针接收者(避免拷贝开销)
  3. 一致性 → 如果类型有任何一个方法是指针接收者,所有方法都应使用指针接收者(避免混淆)

Go 的自动适配

Go 会自动处理调用时的适配,你不需要手动关心:

st := Student{Score: 98}
st.GetScore()   // 值变量调用值方法 ✅
st.SetScore(100) // 值变量调用指针方法 ✅(Go 自动取地址 &st)

ptr := &Student{Score: 98}
ptr.GetScore()   // 指针调用值方法 ✅(Go 自动解引用 *ptr)
ptr.SetScore(100) // 指针调用指针方法 ✅

[!warning] ⚠️ 但是:不能对值取地址调用指针方法

Student{Score: 98}.SetScore(100)  // ❌ 编译错误:无法取字面量的地址

任意类型都可以有方法

不仅限于 struct——可以为当前包内定义的类型别名添加方法:

type MyInt int

func (m MyInt) Abs() MyInt {
    if m < 0 {
        return -m
    }
    return m
}

var x MyInt = -42
fmt.Println(x.Abs())  // 42

[!info] ℹ️ 限制 你不能为不在同一包定义的类型添加方法。所以不能给 int 或 map[string]int 直接加方法。

嵌入:Go 的"继承"机制

Go 没有继承,但通过匿名嵌入实现类似效果:

type People struct {
    Name string
    Age  int
}

func (p *People) Greet() string {
    return fmt.Sprintf("Hi, I'm %s", p.Name)
}

type Student struct {
    ID    int
    Score int
    People          // 匿名嵌入 — 获得 Name、Age 属性和 Greet() 方法
}

st := Student{
    People: People{Name: "Alice", Age: 18},
    ID:     100,
}
fmt.Println(st.Greet())  // Hi, I'm Alice — 方法"继承"了!
fmt.Println(st.Name)     // Alice — 属性也提升了

[!note] 📝 方法提升(Method Embedding) 嵌入类型的方法会自动"提升"到外层类型上。调用 st.Greet() 等价于 st.People.Greet(),但前者更简洁。

覆盖嵌入方法

外层类型可以定义同名方法,覆盖嵌入的方法:

func (s Student) Greet() string {
    return fmt.Sprintf("Student %d says hi", s.ID)
}
// st.Greet() 现在调用的是 Student 版本,而非 People 版本

关键要点总结

场景 推荐
方法需要修改 receiver 指针接收者 *T
receiver 是 map/slice/chan 值接收者即可(它们本身就是引用)
receiver 是大 struct 指针接收者 *T
receiver 是小 struct 且不变 值接收者 T

[!tip] 💡 一致性原则 如果一个类型的某些方法需要指针 receiver,那么全部方法都应该用指针 receiver。混合使用会让调用者困惑。