方法的 receiver 是它所操作的值,在方法名之前声明。它可以是 值接收器(操作副本)或 指针接收器(操作原始值,可以修改它)。正确的选择对于正确性(突变)和性能都很重要。
值接收器 — 操作副本
go
Counter { count }
Increment() {
c.count++
}
c := Counter{}
c.Increment()
fmt.Println(c.count)
值接收器获得结构体的副本,所以修改不会影响原始值 — 适用于只读方法,但如果你想要进行突变就是一个 bug。
func (c *Counter) Increment() { // POINTER receiver — c points to the original
c.count++ // modifies the ACTUAL struct
}
c := Counter{}
c.Increment() // Go automatically takes &c
fmt.Println(c.count) // 1 — the original WAS modified
指针接收器操作实际的结构体,所以它可以 修改 它。Go 会自动获取地址(c.Increment() 即使在值上也有效)。
Use a POINTER receiver when:
✓ The method needs to MODIFY the receiver (mutation)
✓ The struct is LARGE (avoid copying it on every call — performance)
✓ For CONSISTENCY (see below)
Use a VALUE receiver when:
✓ The method only READS, the type is small, and you want copy semantics
✓ The type is naturally a value (small structs, basic types)
// ✅ be consistent — if ANY method needs a pointer receiver, use pointer for ALL
func (c *Counter) Increment() { c.count++ }
func (c *Counter) Value() int { return c.count } // pointer too, for consistency
在一个类型上混合使用值和指针接收器会导致细微的问题 — 约定是选择一个(通常如果任何方法进行突变则使用指针),并在所有方法中使用它。
type Incrementer interface { Increment() }
// if Increment has a POINTER receiver, only *Counter satisfies the interface, not Counter
var i Incrementer = &Counter{} // ✅ works
var j Incrementer = Counter{} // ❌ Counter (value) does NOT satisfy it
这是一个常见的陷阱:使用指针接收器方法时,只有指针类型满足接口。
值接收器 vs 指针接收器的选择是一个基本的、经常被误解的 Go 决策,它影响正确性、性能和接口满足。
最常见的 bug 是 使用值接收器当你想进行突变时 — 修改会悄无声息地仅影响副本,所以更改"不会保留"。指针接收器对于突变是必需的,对于大型结构体也是推荐的(避免每次调用时昂贵的副本)。
一致性原则(在一个类型的所有方法中使用相同的接收器类型)避免了混淆,而 接口满足的细微差别(指针接收器方法意味着只有指针类型满足接口)是导致"does not implement interface"错误的典型陷阱。
理解这些对于编写正确、高效的 Go 代码至关重要,也是最常见的实际和面试话题之一,因为这个选择有非显而易见的后果。