这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天。
1. 接口(多态)
接口的定义:
type 接口名 interface {
方法1(参数列表) 返回值列表
方法2(参数列表) 返回值列表
方法3(参数列表) 返回值列表
// ...
}
- 接口中只能有一系列没有方法体的方法,不能有变量
- 方法的定义也不用
func关键字 - Go 语言中接口不需要显示的实现,只要一个自定义类型,其所关联的方法中包括全部的该接口中的方法,那么这个自定义类型就是实现了这个接口。
- 只要是自定义类型就可以实现接口,不仅仅是结构体;自定义的函数类型也可以实现接口。
- 接口本身不能创建实例。一个自定义类型实现了一个接口,就可以将该自定义类型的实例赋值给接口类型
- 自定义类型可以实现多个接口
- 接口可以组合(继承)一个或多个接口
- 声明了一个接口类型的变量后,其默认值是
nil - 空接口
interface{}被所有类型所实现,可以把任何一个变量赋值给空接口类型 - 与其他语言不同,Go 中接口的使用者才需要知道接口的类型,而接口的实现者根本没有感知到接口的存在
// 接口的实现者1
type SchoolProvider struct {
}
func (SchoolProvider) Get() string {
return "School"
}
// 接口的实现者2
type WaterProvider struct {
}
func (WaterProvider) Get() string {
return "Water"
}
type Provider interface {
Get() string
}
// 接口的使用者
func main() {
var provider1 Provider = SchoolProvider{}
fmt.Println(provider1.Get()) // School
var provider2 Provider = WaterProvider{}
fmt.Println(provider2.Get()) // Water
fmt.Printf("%T %v\n", provider1, provider1) // main.SchoolProvider {}
fmt.Printf("%T %v\n", provider2, provider2) // main.WaterProvider {}
}
1.1. 类型断言
判断和转换接口类型:
type SchoolProvider struct {
B string
}
func (SchoolProvider) Get() string {
return "School"
}
type WaterProvider struct {
A string
}
func (WaterProvider) Get() string {
return "Water"
}
func main() {
var provider1 Provider = SchoolProvider{"B"}
var provider2 Provider = WaterProvider{"A"}
// switch
determineType(provider1) // SchoolProviderB
determineType(provider2) // WaterProviderA
// if
if p, ok := provider2.(WaterProvider); ok {
fmt.Println("WaterProvider" + p.A) // WaterProviderA
}
}
func determineType(provider Provider) {
switch p := provider.(type) {
case WaterProvider:
fmt.Println("WaterProvider" + p.A)
case SchoolProvider:
fmt.Println("SchoolProvider" + p.B)
default:
fmt.Println("Other Provider")
}
}
1.2. 接口内存结构
// $GOPATH/src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.
type itab struct {
inter *interfacetype // 接口的类型
_type *_type // 具体的结构体的类型
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
// 实现了的方法
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
// 空接口
type eface struct {
_type *_type
data unsafe.Pointer
}
- 接口内存结构中已经有一个指向接口实现者具体类型实例的指针了,所以一般不需要使用接口的指针(二级指针)。
- 接口既可以接受实现者实例,也可接受实现者实例的指针。
- 但要注意实现者的方法的接收者的类型。
type AProvider struct {
}
func (AProvider) Get() string {
return "A"
}
type BProvider struct {
}
// 注意这里接收者用的指针!!!
func (*BProvider) Get() string {
return "B"
}
type Provider interface {
Get() string
}
func main() {
{
var a Provider = AProvider{}
var b Provider = BProvider{} // cannot use BProvider{} (value of type BProvider) as type Provider in variable declaration:
// BProvider does not implement Provider (Get method has pointer receiver)
a.Get()
// b.Get()
fmt.Println(a, b)
}
{
var a Provider = &AProvider{} // 这里没问题, 指针可以转换到实例
var b Provider = &BProvider{}
a.Get()
b.Get()
fmt.Println(a, b)
}
}
Go 的指针可以获得其所指向的具体值,而反过来不行。
因为,一个具体值可以被多个指针所指向,但一个指针只会指向一个具体的值。
1.2.1. interface{}
空接口类型可以接受任何类型的变量
func printAny(x interface{}) {
fmt.Println(x)
}
printAny(1)
printAny('A')
printAny("hello")
printAny(WaterProvider{})
printAny(&SchoolProvider{})
// 1
// 65
// hello
// {}
// &{}
// [1 2 3 4]
但是每个元素为空接口类型的切片类型,并不能接受任何类型的切片。
引文 arr 是一个切片类型 []interface{}
func printArr(arr []interface{}) {
fmt.Println(arr)
}
printArr([]int{1, 2, 3, 4}) // 无法将类型 []int 用作类型 []interface{}
// 必须这样
ints := []int{1, 2, 3, 4}
is := make([]interface{}, len(ints))
for i := range ints {
is[i] = ints[i]
}
printArr(is) // [1 2 3 4]
2. nil 空接口 空结构体
-
nil表示空,但不一定是指针。实际上nil是一个变量。是pointer, channel, func, interface, map, or slice type这六种类型的零值,每种类型的nil是不同的,无法比较。// nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. var nil Type // Type must be a pointer, channel, func, interface, map, or slice typetype eface struct { _type *_type data unsafe.Pointer }var p *int fmt.Println(p, p == nil) // <nil> true var m map[string]string fmt.Println(m, m == nil) // map[] true var s []int fmt.Println(s, s == nil) // [] true var c chan string fmt.Println(c, c == nil) // <nil> true var i interface{} fmt.Println(i, i == nil) // <nil> true var f func() fmt.Println(f, f == nil) // <nil> true -
空结构体是 Go 中的一种特殊类型。空结构体的值不是
nil,空结构体的指针也不是nil,都是一个相同的zerobase。 -
空接口不一定是
nil,其零值是nil,一旦有了类型信息就不是nil了。var p *int var i interface{} fmt.Println(i, i == nil) // <nil> true fmt.Println(p, p == nil) // <nil> true i = p // 赋值后会创建一个 eface 结构体, _type 就有值了 fmt.Println(i, i == nil) // <nil> false fmt.Println(p, p == nil) // <nil> true