Go 面向对象编程 2 | 青训营笔记

57 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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{}

zhuanlan.zhihu.com/p/63219494

空接口类型可以接受任何类型的变量

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 type      
    
    type 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