[toc]
Go 接口
多态是指带么可以根据类型的具体实现采取不同行为的能力。如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值。
接口时用来定义行为的类型,这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。
接口的实现
接口值时一个两个字长度的数据结构。
- 第一个字包含了一个指向内部表的指针。这个内部表叫做iTable,包含了所存储值的类型信息以及与这个值相关的方法。
- 第二个字包含时一个指向所存储值的指针。将类型信息和指针组合在一起。
下面时对于值和指针赋值后接口值得简图
值调用
//定义接口
type notifier interface {
notify()
}
type user struct {
name string
email string
}
// 值实现接口的方法
func (u user) notify() {
fmt.Printf(u.name + u.email)
}
// 接口方法的调用
func sendNotification(n notifier) {
n.notify()
}
func main() {
u := user{"smith", "smith@email.com"}
sendNotification(u)
}
指针调用
func (u *user) notify() {
fmt.Printf(u.name + u.email)
}
func main() {
u := &user{"smith", "smith@email.com"}
sendNotification(u)
}
值得注意的是,使用指针接受者的实现方法,不能被以值得方式调用。即如下调用是无法通过编译的。
func (u *user) notify() {
fmt.Printf(u.name + u.email)
}
func main() {
//无法通过编译
u := user{"smith", "smith@email.com"}
sendNotification(u)
}
方法集
为什么无法通过值来使用指针接受者的实现方法?
方法集定义了一组关联到给定类型的值或者指针的方法。定义方法时使用的接受者的类型决定了这个方法是关联到值还是关联到指针,还是两个都关联。
对于Go而言,对于值T实现的方法,
- T类型的值的方法集只包含值接受者声明的方法。
- 而指向T类型的指针的方法集即包含值接受者声明的方法,也包含指针接受者声明的方法。
从接受者的视角看,
- 使用指针接受者来实现一个接口,那么只有指向那个类型的指针才能实现对应的接口。
- 使用值接受者来实现一个接口,那么那个类型的值和指针都能偶实现对应的接口。
为什么会有无法通过值来使用指针接受者的实现方法这种限制?
事实上,编译器并不是总能自动获得一个值的地址。所以值的方法集只包括了使用值接受者实现的方法。
为什么编译器并不总能获取到一个值的地址?
- 不能获取到地址,说明对于Go来讲,这个值是不可寻址的。不可寻址可以理解为不可修改或者是寻址没有意义。
- 对于常量而言,没法取指针是合理的,如果常量能取到指针,那就意味着可以修改,也就不能叫常量了。
- 同样map元素的也是不可寻址的,主要有两个原因:
- 如果对象不存在,返回零值,零值是不可变对象。因此不可寻址
- 如果对象存在,又因为元素的地址是可以变化的。因此寻址结果没有意义
- 字符串中的字符/字节又不能寻址是因为字符串是不可变的
参考:https://colobu.com/2018/02/27/go-addressable/