Golang 不可寻址的理解
Table of Contents
如果没有听过 Golang 的不可寻址(not addressable)相关概念,没有关系,从字面上看,就是无法获取值的地址。先举个简单的例子:
package main
import "fmt"
func main() {
m := map[int]string{0: "hello"}
fmt.Printf("%p", &m[0])
}
m
是 map[int]string
类型,只包含一个键值对,打印这个键值对的值的地址,直接编译错误:cannot take the address of m[0]
,无法获取 map
中值的地址。这是个比较常见的不可寻址的例子。原因也比较简单,map
类型是通过哈希表实现的,随着 map
元素的增多,可能触发扩容,那么 map
的值的位置发生改变,即其地址会发生变化,所以无法对 map
的值寻址。另一方面,如果元素不存在于map
中,返回零值,而零值是不可变对象,是不能寻址的(golang 中不可变对象是不可寻址的,如常量)。
再看个稍微复杂的例子,在Golang wiki 的 MethodSets 有这样一句话:
The concrete value stored in an interface is not addressable, in the same way that a map element is not addressable.
意思具体值赋值给 interface
类型后与 map
中的元素一样是不可寻址的。先不去理解这句话,先看个例子:
package main
const NewName = "run.wu"
type Male struct {
Name string
}
func (m Male) getName() string {
return m.Name
}
func (m *Male) setName(name string) {
m.Name = name
}
func main() {
m1 := Male{}
m1.getName()
m1.setName(NewName)
m2 := &Male{}
m2.getName()
m2.setName(NewName)
}
定义了一个 Male
类型,有两个方法,一个是值接收者的 getName()
方法,一个是指针接收者 setName()
方法。在 main()
中,m1
是 Male
类型的值对象,m2
是 Male
类型的指针对象,在这两个对象上都调用了 getName()
和 setName()
方法,可以正常调用执行。那么问题来了:
m1
是值类型对象(调用者),为什么可以调用指针接收者方法setName()
?m2
是指针类型对象(调用者),什么可以调用值接收者方法getName()
?
对于第一个问题,值调用者调用指针接收者方法时, 编译器默认会使用调用者的引用(取地址)来调用方法,即编译器隐示转换 成 (&m1).setName()
对于第二个问题,指针调用者调用值接收者方法时,编译器默认会将指针调用者解引用(取值)为值类型,即编译器隐示转换成 (*m2).getName()
上面的例子还是比较好理解的,很多书和文章都有解释这个问题。
那再看另一个例子:
package main
const NewName = "run.wu"
type Person interface {
getName() string
setName(name string)
}
type Male struct {
Name string
}
func (m Male) getName() string {
return m.Name
}
func (m *Male) setName(name string) {
m.Name = name
}
func main() {
var p1 Person = Male{} // error
p1.getName()
p1.setName(NewName)
var p2 Person = &Male{}
p2.getName()
p2.setName(NewName)
}
这个例子和刚刚的例子很类似,不同点的是,增加了一个 Person
类型的接口,定义了 Male
类型,实现了值接收者的 getName()
方法,实现了指针接收者的 setName()
方法。
初始化 Male
的值对象赋值给 Person
接口,记作 p1
,直接报错:
cannot use Male literal (type Male) as type Person in assignment:
Male does not implement Person (setName method has pointer receiver)
错误内容是,Male
类型的变量不能赋值给 Person
,因为 Male
类型没有实现 Person
接口(setName
是指针接收者方法)。
那为什么之前的例子中,编译器可以自动将值类型(非接口类型)取地址做隐示转换,而这里就不可以了?原因就是开头那就话:
The concrete value stored in an interface is not addressable.
值类型赋值给接口,是不可寻址的,既然不可寻址,编译器也就没办法自动取其地址传给指针接收的方法了。