Value Receivers vs Pointer Receivers in Go(实用解释)
Source: Dev.to
Introduction
在 Go 中,你首先会遇到的设计问题之一是:方法应该使用 值接收者 还是 指针接收者。这两种形式都很常见:
func (u User) Greet() {}
func (u *User) UpdateName() {}
虽然看起来相似,但选择会影响代码的正确性、性能以及类型在实际后端代码中的行为。
Value Receivers
在 Go 中,方法只是附加到类型上的函数。
type User struct {
Name string
}
使用值接收者添加行为:
func (u User) Greet() {
fmt.Println("Hello,", u.Name)
}
这里的 u 被称为 接收者;它是调用方法的值的副本。
user := User{Name: "Shivam"}
user.Greet()
Modifying a Value Receiver
当方法接收到的是副本时,任何修改只会影响该副本。
func (u User) ChangeName() {
u.Name = "New Name"
}
示例
package main
import "fmt"
type User struct {
Name string
}
func (u User) ChangeName() {
u.Name = "New Name"
}
func main() {
user := User{Name: "Shivam"}
user.ChangeName()
fmt.Println(user.Name) // Output: Shivam
}
输出仍然是 Shivam,因为方法作用于副本。
关键点: 值接收者 = 在副本上工作。
When to Use Value Receivers
- 结构体很小。
- 方法不需要修改接收者(只读辅助函数)。
一个典型的例子是 Point 类型:
func (p Point) Distance() float64 { /* ... */ }
Pointer Receivers
指针接收者让方法能够访问内存中的原始结构体。
func (u *User) ChangeName() {
u.Name = "New Name"
}
示例
package main
import "fmt"
type User struct {
Name string
}
func (u *User) ChangeName() {
u.Name = "New Name"
}
func main() {
user := User{Name: "Shivam"}
user.ChangeName()
fmt.Println(user.Name) // Output: New Name
}
更改会持久化,因为方法作用于原始对象。
关键点: 指针接收者 = 在原始对象上工作。
Advantages of Pointer Receivers
- 状态修改: 需要更新字段的方法几乎总是应该使用指针接收者。
- 性能: 对于大型结构体,避免在每次方法调用时进行昂贵的复制。
- 接口实现: 带指针接收者的方法只属于指针类型,这会影响类型是否满足某个接口。
Choosing Between Value and Pointer Receivers
| Situation | Recommended Receiver |
|---|---|
| 方法必须修改接收者的字段 | 指针接收者 (*T) |
| 结构体很大,复制成本高 | 指针接收者 |
| 方法仅读取数据且结构体很小 | 值接收者 (T) |
| 需要实现要求指针方法的接口 | 指针接收者 |
在实际项目中,对于表示后端系统真实实体的类型(服务、处理器、配置、数据库模型、控制器),指针接收者是默认选择。对于行为类似不可变数据的简单值类型,使用值接收者即可。
Summary
- 值接收者: 在副本上工作;方法内部的修改不会反映到外部。
- 指针接收者: 在原始对象上工作;修改会持久化。
- 当需要修改状态、避免复制大型结构体或满足需要指针方法的接口时,使用指针接收者。
- 对于小型、只读的类型,使用值接收者。
一旦你内化了这一区别,在 Go 中设计方法就会变得直观易懂。