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

157 lines
4.3 KiB
Markdown
Raw Normal View History

2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
tags: [go, golang, go基础语法, 方法]
create time: 2026-06-07 15:00
2026-06-07 11:08:10 +08:00
---
2026-06-07 12:14:39 +08:00
# Go 语言方法
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
## 概述
方法是绑定在特定类型上的函数。Go 中任何类型(包括内置类型的别名)都可以有方法。本文讲解值接收者 vs 指针接收者的选择规则,以及通过嵌入实现"继承"。
## 正文
### 方法 vs 函数:本质区别是什么?
**方法 = 函数 + 接收者(receiver)**。接收者是该方法所绑定的类型实例。
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
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 — 调用方式像字段访问
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!note] 📝 "struct + 方法 ≈ class"
> Go 没有类(class),但 `struct` 提供数据,`method` 提供行为,组合起来就是面向对象的核心概念。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 值接收者 vs 指针接收者
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
这是 Go 方法设计中最关键的选择:
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
| | 值接收者 `(t T)` | 指针接收者 `(t *T)` |
|--|-----------------|-------------------|
| 传入什么 | 拷贝一份数据 | 传入地址 |
| 能修改原值吗 | ❌ 不能 | ✅ 能 |
| 调用方式 | 两者皆可 | 两者皆可(Go 自动解引用/取地址) |
```go
type Student struct{ Score int }
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
// 值接收者:读取
func (s Student) GetScore() int {
return s.Score
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
// 指针接收者:修改
func (s *Student) SetScore(score int) {
s.Score = score
2026-06-07 11:08:10 +08:00
}
```
2026-06-07 12:14:39 +08:00
> [!tip] 💡 选择规则
> 1. **需要修改原值** → 必须用指针接收者
> 2. **结构体较大** → 优先用指针接收者(避免拷贝开销)
> 3. **一致性** → 如果类型有任何一个方法是指针接收者,**所有方法都应使用指针接收者**(避免混淆)
#### Go 的自动适配
Go 会自动处理调用时的适配,你不需要手动关心:
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
st := Student{Score: 98}
st.GetScore() // 值变量调用值方法 ✅
st.SetScore(100) // 值变量调用指针方法 ✅(Go 自动取地址 &st)
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
ptr := &Student{Score: 98}
ptr.GetScore() // 指针调用值方法 ✅(Go 自动解引用 *ptr)
ptr.SetScore(100) // 指针调用指针方法 ✅
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!warning] ⚠️ 但是:不能对值取地址调用指针方法
> ```go
> Student{Score: 98}.SetScore(100) // ❌ 编译错误:无法取字面量的地址
> ```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 任意类型都可以有方法
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
不仅限于 struct——可以为当前包内定义的**类型别名**添加方法:
2026-06-07 11:08:10 +08:00
```go
2026-06-07 12:14:39 +08:00
type MyInt int
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
func (m MyInt) Abs() MyInt {
if m < 0 {
return -m
}
return m
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
var x MyInt = -42
fmt.Println(x.Abs()) // 42
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!info] ℹ️ 限制
> 你不能为不在同一包定义的类型添加方法。所以不能给 `int` 或 `map[string]int` 直接加方法。
### 嵌入:Go 的"继承"机制
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
Go 没有继承,但通过**匿名嵌入**实现类似效果:
```go
2026-06-07 11:08:10 +08:00
type People struct {
Name string
2026-06-07 12:14:39 +08:00
Age int
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
func (p *People) Greet() string {
return fmt.Sprintf("Hi, I'm %s", p.Name)
2026-06-07 11:08:10 +08:00
}
type Student struct {
2026-06-07 12:14:39 +08:00
ID int
2026-06-07 11:08:10 +08:00
Score int
2026-06-07 12:14:39 +08:00
People // 匿名嵌入 — 获得 Name、Age 属性和 Greet() 方法
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
st := Student{
People: People{Name: "Alice", Age: 18},
ID: 100,
2026-06-07 11:08:10 +08:00
}
2026-06-07 12:14:39 +08:00
fmt.Println(st.Greet()) // Hi, I'm Alice — 方法"继承"了!
fmt.Println(st.Name) // Alice — 属性也提升了
2026-06-07 11:08:10 +08:00
```
2026-06-07 12:14:39 +08:00
> [!note] 📝 方法提升(Method Embedding)
> 嵌入类型的方法会自动"提升"到外层类型上。调用 `st.Greet()` 等价于 `st.People.Greet()`,但前者更简洁。
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
#### 覆盖嵌入方法
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
外层类型可以定义同名方法,覆盖嵌入的方法:
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
```go
func (s Student) Greet() string {
return fmt.Sprintf("Student %d says hi", s.ID)
}
// st.Greet() 现在调用的是 Student 版本,而非 People 版本
```
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
### 关键要点总结
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
| 场景 | 推荐 |
|------|------|
| 方法需要修改 receiver | 指针接收者 `*T` |
| receiver 是 map/slice/chan | 值接收者即可(它们本身就是引用) |
| receiver 是大 struct | 指针接收者 `*T` |
| receiver 是小 struct 且不变 | 值接收者 `T` |
2026-06-07 11:08:10 +08:00
2026-06-07 12:14:39 +08:00
> [!tip] 💡 一致性原则
> 如果一个类型的某些方法需要指针 receiver,那么**全部方法都应该用指针 receiver**。混合使用会让调用者困惑。