GO学习笔记-5 接口

本文详细探讨了Go语言中接口的特性和用法,包括隐式实现、动态类型和值、类型断言以及接口的比较。接口在Go语言中扮演着重要的角色,允许在不修改类型实现的情况下添加新的接口。文章特别强调了类型断言的重要性,它允许开发者从接口中提取底层值并进行类型转换。此外,还介绍了如何使用类型断言来判断错误类型和结构体是否具备特定方法或接口。
摘要由CSDN通过智能技术生成

接口

go 的接口时隐式实现的,即不需声明结构继承了某个接口,只要提供接口所必需的方法,这样就无需改变类型的实现就可以为这些类型创建新的接口

之前的所有面向对象语言的接口都是像契约一样,我们事先定义好接口,再去耦合继承,但我们可能并不知道需求会出现森么情况,为啥不先开发后再提取接口呢


    type Person interface {
        Eat(s []string) int
    }

    type Student interface {
        Person
        Learn(i interface{}) int
    }

    type PersonImpl struct{}

    func (p *PersonImpl) Eat(s []string) int {
        return len(s)
    }
    func (p *PersonImpl) Fight() {
        fmt.Println("fighting!!!")
    }

    func main() {
        var p Person
        pi := &PersonImpl{} //该结构的方法接收者都是指针,所以该结构的指针才实现了 Person 接口
        // 虽然结构变量可以调用指针或非指针接收方法,但是他们是偷偷进行了隐式转换的

        p = pi // 只有实现 p 接口的结构才能赋值
        p.Eat(make([]string, 5, 5))
        //p.Fight() 报错,没有该方法

        var s Student
        p = s // 接口之间也可以相互赋值,必须满足接口
    }

接口的类型在编译时就被确定,但是接口实际有两个值:动态类型,动态值


    type Test interface {
        Tester()
    }

    type MyFloat float64

    func (m MyFloat) Tester()  {
        fmt.Println(m)
    }

    func describe(t Test)  {
        fmt.Printf("Interface type %T value %v\n",t,t)
    }

    func main() {
    var t Test
    m := MyFloat(66.6)
    t = m
    fmt.Println(t) //动态值
    t.Tester() //动态类型的方法
    describe(t) // Interface type main.MyFloat value 66.6
}

注意:我们在讲方法时,方法有两种调用情况,普通调用和指针调用,指针可以调用一切方法,而结构体变量不能调用指针方法,在实现接口时也是如此,如果是用指针方法实现的该接口,那么只能将指针赋值给接口,再调用,可以看做结构体变量没有这个方法


    type Test interface {
        Tester()
    }

    type MyFloat float64

    func (m MyFloat) Tester()  {
        fmt.Println(m)
    }

    type MyInt int

    func (this *MyInt) Tester()  {
        fmt.Println(this)
    }

    func main() {
        var t Test
        mf := MyFloat(66.6)
        t = mf
        t.Tester()

        mi := MyInt(100)
        //t = mi  报错,因为 mi 并不能调用 Tester 方法
        t = &mi
        t.Tester()
    }

接口的零值,这两个值都是 nil,用 nil 调用接口方法则会宕机,我们可以将接口与 nil 做 == 检查


    var w io.Writer
    w = os.Stdin //此时 w 的动态类型是 os.Stdin 实现的接口,他的动态值是指向 os.Stdin 的一个指针
    fmt.Printf("%T",w) // *os.File
    w.Write([]byte("kanggege")) // 调用 w 实际是调用 (*os.File).Write()
    // 但我们在编译时无法确认接口的动态类型是什么,实际还是通过动态分发,以动态值为接受者调用动态类型的方法

接口值可以用 == 做比较,如果两个借口值都是 nil,或二者的动态类型完全一致且动态值相等,则接口值相等,即接口值可以作为 map 的 key,或 switch 的操作数

如果接口的动态类型一致,但动态值不可比较,则这个比较会崩溃,在作比较时要小心!

一个易忽略的错误


    const debug = true
    var buf *bytes.Buffer
    if debug {
        buf = new(bytes.Buffer)
    }
    f(buf)


    func f (out io.Writer)  {
        if out != nil {
            out.Write([]byte("done\n"))
        }
    }

分析下,buf,它是一个结构体指针类型,当作为 io.Write 赋值时,它已经有了动态类型 *file.Write 但是没有动态值,即使这样,在做 nil 判断时,它也是非 nil 类型,如果 debug 为 false 时,也会调用 out.Write 但 out 的动态值为 nil,则会宕机,应该在起始时就将 buf 设置为 io.Write

类型断言

在说类型断言之前,我们不得不提一提 interface{} 他可以说是 java 的 object,所有的类型都直接或间接实现了它,所以我们在声明类型时可以这样写

    func f (i interface{})  {
        index := strings.LastIndex(i)
        fmt.Println(i)
    }
    map[interface{}]interface{}

但是按照第一个函数的写法会报错,因为 i 并不是 string 类型的,也就是说,我们要先确定并转换 i 的类型,才能去更好的使用它,这有点类似于 java 中类的向下转换,将编译类型转换为运行时类型,即,类型断言用于提取接口的底层值

java 中的向下转换是通过强制类型转换,而 go 中使用断言 x.(T) ,即判断或提取出他的类型再使用

使用断言的前提条件就是,x 必须是接口类型的值
1. 当 T 是具体类型时,会检查 x 的动态类型是否就是 T,如果满足则结果就是 x 的动态值和 T 类型
2. 当 T 是接口时,检查 x 的动态类型是否是 T,如果满足返回的仍是接口 T

断言的使用有两种方式

    i = i.(string) //如果断言失败,则会宕机,除非你确定,否则并不推荐,不过断言为 string 应该不会失败
    i,ok = i.(string) //通过 ok 判断是否成功,失败也没事

接口和类型断言是相辅相成的,有过 java 基础的一定会对结构不能显式标注实现了哪个接口而感到一丝不安,那刚好可以用类型断言来做一个判断,这样虽然虽然会让代码略微冗余,但是有更好的维护性和强健性,多用用就会发现,它远优于 java 的接口

  • 使用断言识别错误类型:为错误类型定义好结构后,在出现错误时,可以使用断言识别不同的错误类型,并做出相应处理
  • 使用断言判别是否具有特性:当无法确定结构是否实现了某个方法或接口时,可以在方法内定义接口,并使用断言判断是否可以调用

断言的另一种使用方式是提取接口的类型,使用 switch

    func findType(i interface{})  {
        switch i.(type) {
        case string:
            fmt.Printf("I am a string and my value is %s\n", i.(string))
        case int:
            fmt.Printf("I am an int and my value is %d\n", i.(int))
        default:
            fmt.Printf("Unknown type\n")
        }
    }

注意:只有在 switch 中才能使用 i.(type) 提取类型,而得到的类型也可能是多个不同的值,比如实现了两个接口的结构体


    type MyFloat float64

    func (m MyFloat) Tester()  {
        fmt.Println("Tester")
    }

    func (m MyFloat) Tester2()  {
        fmt.Println("Tester2")
    }

    func findType(i interface{})  {
        switch i.(type) {
        case Test:
            fmt.Println("Test")
        case Test2:
            fmt.Println("Test2")
        default:
            fmt.Printf("Unknown type\n")
        }
    }

    func main() {
        m := MyFloat(66.6)

        //更换 switch 下选项的位置,也可以打印出 Test2
        findType(m) //Test
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值