vault backup: 2026-06-07 12:14:39
This commit is contained in:
+121
-138
@@ -1,173 +1,156 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- go基础语法
|
||||
- 方法
|
||||
tags: [go, golang, go基础语法, 方法]
|
||||
create time: 2026-06-07 15:00
|
||||
---
|
||||
|
||||
# Go语言方法
|
||||
# Go 语言方法
|
||||
|
||||
## 方法的定义
|
||||
方法是一类特殊的函数,方法是绑定在某种类型的变量上的函数,它有一定的约束范围。而变量的类型不仅仅局限于结构体类型,可以是任意类型,比如`int`、`bool`等的别名类型。
|
||||
有过c++或者java基础的同学可以这样理解,一个`struct`加上绑定在这个类型上的方法就等价于一个类。
|
||||
方法的定义格式如下
|
||||
## 概述
|
||||
|
||||
方法是绑定在特定类型上的函数。Go 中任何类型(包括内置类型的别名)都可以有方法。本文讲解值接收者 vs 指针接收者的选择规则,以及通过嵌入实现"继承"。
|
||||
|
||||
## 正文
|
||||
|
||||
### 方法 vs 函数:本质区别是什么?
|
||||
|
||||
**方法 = 函数 + 接收者(receiver)**。接收者是该方法所绑定的类型实例。
|
||||
|
||||
```go
|
||||
func (变量 类型) 方法名(参数列表)(返回值列表){
|
||||
|
||||
}
|
||||
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 自动解引用/取地址) |
|
||||
|
||||
对比前面我们学习过的函数,方法跟函数的唯一区别就是在`func`关键字和函数名之间加了一个接收者类型和接收者,同样,这个接收者的类型可以是普通变量或者指针变量
|
||||
方法的调用跟结构体属性的调用方式相同,都是通过`.`操作符来完成
|
||||
下面给`Student`结构定义一个方法
|
||||
```go
|
||||
package main
|
||||
type Student struct{ Score int }
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
// 值接收者:读取
|
||||
func (s Student) GetScore() int {
|
||||
return s.Score
|
||||
}
|
||||
|
||||
func (st *Student) GetName() string {
|
||||
return st.Name
|
||||
// 指针接收者:修改
|
||||
func (s *Student) SetScore(score int) {
|
||||
s.Score = score
|
||||
}
|
||||
```
|
||||
|
||||
func main() {
|
||||
st := &Student{
|
||||
ID : 100,
|
||||
Name : "zhangsan",
|
||||
Age : 18,
|
||||
Score : 98,
|
||||
> [!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
|
||||
}
|
||||
fmt.Printf("学生st的姓名是: %s\n", st.GetName()) // 调用Student的方法GetName
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
学生st的姓名是: zhangsan
|
||||
```
|
||||
|
||||
## 方法的调用
|
||||
定义在指针类型或者值类型上的方法可以通过指针变量或者值变量来调用,可能读起来有点绕,下面看例子,会更清晰
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
return m
|
||||
}
|
||||
|
||||
func (st *Student) SetScore(score int) {
|
||||
st.Score = score
|
||||
}
|
||||
var x MyInt = -42
|
||||
fmt.Println(x.Abs()) // 42
|
||||
```
|
||||
|
||||
func (st Student) GetScore() int{
|
||||
return st.Score
|
||||
}
|
||||
> [!info] ℹ️ 限制
|
||||
> 你不能为不在同一包定义的类型添加方法。所以不能给 `int` 或 `map[string]int` 直接加方法。
|
||||
|
||||
### 嵌入:Go 的"继承"机制
|
||||
|
||||
Go 没有继承,但通过**匿名嵌入**实现类似效果:
|
||||
|
||||
func main() {
|
||||
st := &Student{
|
||||
ID : 100,
|
||||
Name : "zhangsan",
|
||||
Age : 18,
|
||||
Score : 98,
|
||||
}
|
||||
fmt.Printf("设置前,学生st的分数是: %d\n", st.GetScore()) // 通过指针调用定义在值类型的方法GetScore
|
||||
st.SetScore(100) // 通过指针调用定义在指针类型上的方法
|
||||
fmt.Printf("设置后,学生st的分数是: %d\n", st.GetScore()) // 通过指针调用定义在值类型的方法GetScore
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
设置前,学生st的分数是: 98
|
||||
设置后,学生st的分数是: 100
|
||||
```
|
||||
## 继承
|
||||
go语言不像c++或者java一样有显示的继承关系,因为go语言没有类的概念,所以自然就不存在父类,子类一说,自然就没有继承的关系。那么,go语言是如何实现功能的呢?
|
||||
答案是:**组合**,go语言中不论是属性,还是方法的继承都是通过组合来实现的。
|
||||
请看下面例子
|
||||
```go
|
||||
type People struct {
|
||||
Name string
|
||||
Age int
|
||||
Age int
|
||||
}
|
||||
|
||||
func (p *People) Greet() string {
|
||||
return fmt.Sprintf("Hi, I'm %s", p.Name)
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
ID int
|
||||
Score int
|
||||
People // 将People作为Student的一个属性,注意不要加类型,这就是隐式继承
|
||||
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 — 属性也提升了
|
||||
```
|
||||
将原来`Studen`t的定义改为`Student`和`People`两个结构来定义,通过这种组合的方式,其实`Student`就拥有了`People`的`Name`属性和`Age`属性
|
||||
代码展示
|
||||
|
||||
> [!note] 📝 方法提升(Method Embedding)
|
||||
> 嵌入类型的方法会自动"提升"到外层类型上。调用 `st.Greet()` 等价于 `st.People.Greet()`,但前者更简洁。
|
||||
|
||||
#### 覆盖嵌入方法
|
||||
|
||||
外层类型可以定义同名方法,覆盖嵌入的方法:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type People struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func (p *People) GetName() string{
|
||||
return p.Name
|
||||
}
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Score int
|
||||
People // 将People作为Student的一个属性,注意不要加类型,这就是隐式继承
|
||||
}
|
||||
|
||||
func (st *Student) SetScore(score int) {
|
||||
st.Score = score
|
||||
}
|
||||
|
||||
func (st Student) GetScore() int{
|
||||
return st.Score
|
||||
}
|
||||
|
||||
func main() {
|
||||
st := &Student{
|
||||
ID : 100,
|
||||
Score: 98,
|
||||
People : People {
|
||||
Name : "zhangsan",
|
||||
Age : 18,
|
||||
},
|
||||
}
|
||||
fmt.Printf("学生st的分数是: %d\n", st.GetScore()) // 通过指针调用定义在值类型的方法GetScore // 通过指针调用定义在指针类型上的方法
|
||||
fmt.Printf("学生st的姓名是: %s\n", st.GetName())
|
||||
func (s Student) Greet() string {
|
||||
return fmt.Sprintf("Student %d says hi", s.ID)
|
||||
}
|
||||
// st.Greet() 现在调用的是 Student 版本,而非 People 版本
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
学生st的分数是: 98
|
||||
学生st的姓名是: zhangsan
|
||||
```
|
||||
这个例子中,`Student`通过内嵌`People`获得了`People`的`Name`和`Age`属性以及`GetName`方法。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 关键要点总结
|
||||
|
||||
| 场景 | 推荐 |
|
||||
|------|------|
|
||||
| 方法需要修改 receiver | 指针接收者 `*T` |
|
||||
| receiver 是 map/slice/chan | 值接收者即可(它们本身就是引用) |
|
||||
| receiver 是大 struct | 指针接收者 `*T` |
|
||||
| receiver 是小 struct 且不变 | 值接收者 `T` |
|
||||
|
||||
> [!tip] 💡 一致性原则
|
||||
> 如果一个类型的某些方法需要指针 receiver,那么**全部方法都应该用指针 receiver**。混合使用会让调用者困惑。
|
||||
|
||||
Reference in New Issue
Block a user