⏺ Go 编译器隐式取地址的完整细节

场景回顾

ScaleBy 是指针接收器方法,按理说只有 *Point 才能调用:

1
2
3
4
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}

但你可以这样写:

1
2
p := Point{1, 2}
p.ScaleBy(2) // p 是 Point,不是 *Point,居然能调用?

这是因为编译器帮你做了隐式转换,实际执行的是:

1
(&p).ScaleBy(2)       // 编译器自动取地址

隐式转换其实有两个方向

方向一:值调用指针方法 → 自动取地址 &

1
2
3
4
5
p := Point{1, 2}

p.ScaleBy(2)
// 编译器翻译为:
(&p).ScaleBy(2)

**方向二:指针调用值方法 → 自动解引用 ***

1
2
3
4
5
pptr := &Point{1, 2}                                                                   

pptr.Distance(q)
// 编译器翻译为:
(*pptr).Distance(q)

总结就是:不管你拿着值还是指针,编译器都会自动帮你匹配到正确的接收器类型。


关键限制:必须能取到地址

隐式取 & 有一个前提——变量必须是可寻址的(addressable)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ✅ 变量 — 可寻址
p := Point{1, 2}
p.ScaleBy(2) // OK,编译器能取 &p

// ✅ 结构体字段 — 可寻址
type Rect struct{ Origin Point }
r := Rect{Point{1, 2}}
r.Origin.ScaleBy(2) // OK,编译器能取 &r.Origin

// ✅ 切片元素 — 可寻址
points := []Point{{1, 2}}
points[0].ScaleBy(2) // OK,编译器能取 &points[0]

// ❌ 临时值(字面量)— 不可寻址
Point{1, 2}.ScaleBy(2) // 编译错误!

为什么临时值不行? 因为 Point{1, 2} 是一个没有存在任何地方的临时值,没有内存地址可取。
取地址的前提是这个值得”住”在某个地方(变量、字段、元素)。


完整示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Point struct{ X, Y float64 }

// 值接收器方法
func (p Point) Distance(q Point) float64 { ... }

// 指针接收器方法
func (p *Point) ScaleBy(factor float64) { ... }

p := Point{1, 2}
pptr := &p

// 四种调用组合,全部合法:
p.Distance(q) // 值调值方法 → 直接调用
p.ScaleBy(2) // 值调指针方法 → (&p).ScaleBy(2)
pptr.Distance(q) // 指针调值方法 → (*pptr).Distance(q)
pptr.ScaleBy(2) // 指针调指针方法 → 直接调用
你手里有 方法需要 编译器做什么
Point Point 直接调
Point *Point 自动 & 取地址
*Point Point 自动 * 解引用
*Point *Point 直接调

一句话理解

Go 编译器的态度是:你别管值和指针的转换了,只要变量有地址,我帮你搞定。
这就是为什么日常写 Go 代码时,你几乎不需要显式写 (&p).Method() 这种丑陋的语法。