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

157 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, golang, go基础语法, 方法]
create time: 2026-06-07 15:00
---
# Go 语言方法
## 概述
方法是绑定在特定类型上的函数。Go 中任何类型(包括内置类型的别名)都可以有方法。本文讲解值接收者 vs 指针接收者的选择规则,以及通过嵌入实现"继承"。
## 正文
### 方法 vs 函数:本质区别是什么?
**方法 = 函数 + 接收者(receiver)**。接收者是该方法所绑定的类型实例。
```go
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 自动解引用/取地址) |
```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 会自动处理调用时的适配,你不需要手动关心:
```go
st := Student{Score: 98}
st.GetScore() // 值变量调用值方法 ✅
st.SetScore(100) // 值变量调用指针方法 ✅(Go 自动取地址 &st)
ptr := &Student{Score: 98}
ptr.GetScore() // 指针调用值方法 ✅(Go 自动解引用 *ptr)
ptr.SetScore(100) // 指针调用指针方法 ✅
```
> [!warning] ⚠️ 但是:不能对值取地址调用指针方法
> ```go
> Student{Score: 98}.SetScore(100) // ❌ 编译错误:无法取字面量的地址
> ```
### 任意类型都可以有方法
不仅限于 struct——可以为当前包内定义的**类型别名**添加方法:
```go
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 没有继承,但通过**匿名嵌入**实现类似效果:
```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()`,但前者更简洁。
#### 覆盖嵌入方法
外层类型可以定义同名方法,覆盖嵌入的方法:
```go
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**。混合使用会让调用者困惑。