interface特性

本文详细探讨了Go语言中interface与struct的区别,包括值和指针调用方法的不同。同时,介绍了iface和eface的区别,以及接口的判空和打印动态类型及值的方法。此外,文章还揭示了结构体隐式转换在接口使用中的影响。
摘要由CSDN通过智能技术生成

一. interface与struct的调用区别

struct特性:
说明struct,在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法
interface不行,必须严格按照特性,比如

例子一. 不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。
package main

import "fmt"

type Person struct {
    age int
}

func (p Person) howOld() int {
    return p.age
}

func (p *Person) growUp() {
    p.age += 1
}

func main() {
    // qcrao 是值类型
    qcrao := Person{age: 18}

    // 值类型 调用接收者也是值类型的方法
    fmt.Println(qcrao.howOld())

    // 值类型 调用接收者是指针类型的方法
    qcrao.growUp()
    fmt.Println(qcrao.howOld())

    // ----------------------

    // stefno 是指针类型
    stefno := &Person{age: 100}

    // 指针类型 调用接收者是值类型的方法
    fmt.Println(stefno.howOld())

    // 指针类型 调用接收者也是指针类型的方法
    stefno.growUp()
    fmt.Println(stefno.howOld())
}

输出结果:

18
19
100
101
-值接收者指针接收者
值类型调用者方法会使用调用者的一个副本,类似于“传值”使用值的引用来调用方法,上例中,qcrao.growUp() 实际上是 (&qcrao).growUp()
指针类型调用者指针被解引用为值,上例中,stefno.howOld() 实际上是 (*stefno).howOld()实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针
例子二. 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法

当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者
通俗的话理解:
在下面的例子中func (p *Gopher) debug()这种实现有了之后,不会自动生成func (p Gopher) debug() ,但是如果实现了func (p Gopher) code(),会隐式实现func (p *Gopher) code(), 因为自动生成的话会影响调用者。

package main

import "fmt"

type coder interface {
    code()
    debug()
}

type Gopher struct {
    language string
}

func (p Gopher) code() {
    fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
    fmt.Printf("I am debuging %s language pointer\n", p.language)
}

func main() {
    var c coder = &Gopher{"Go"}
    c.code()
    c.debug()

    var gopher Gopher = Gopher{"Go"}
    gopher.code()
    /*下面两者等同*/
    (&gopher).debug()
    gopher.debug()
}

注意23行,如果不写&,那么会出现错误

二. iface 和 eface

1. 两者区别是什么

iface 和 eface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}
查看源码文件
/usr/local/Cellar/go@1.17/1.17.10/libexec/src/runtime/runtime2.go

type iface struct {
   tab  *itab
   data unsafe.Pointer
}

type eface struct {
   _type *_type
   data  unsafe.Pointer
}

其中iface中的itab是指向

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 字段描述了实体的类型,包括内存对齐方式,大小等;
inter 字段则描述了接口的类型。
fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。

在这里插入图片描述

2. 查看汇编代码

go tool compile -S test03.go
在这里插入图片描述

三. 接口判空以及打印

1. 接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil
package main

import "fmt"

type Coder interface {
   code()
}

type Gopher struct {
   name string
}

func (g Gopher) code() {
   fmt.Printf("%s is coding\n", g.name)
}

func main() {
   var c Coder
   fmt.Println(c == nil)
   fmt.Printf("c: %T, %v\n", c, c)
   var g *Gopher
   fmt.Println(g == nil)
   fmt.Printf("g: %T, %v\n", g, g)

   fmt.Println("====================")

   /*接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil*/
   c = g
   fmt.Println(c == nil)
   fmt.Printf("c: %T, %v\n", c, c)
}

输出结果

true
c: <nil>, <nil>
true
g: *main.Gopher, <nil>
====================
false
c: *main.Gopher, <nil>
2. 结构题隐式转换的判断
package main

import "fmt"

type MyError struct {}

func (i MyError) Error() string {
    return "MyError"
}

func main() {
    err := Process()
    fmt.Println(err)

    fmt.Println(err == nil)
}

func Process() error {
    var err *MyError = nil
    return err
}

打印结果

<nil>
false
3. 如何打印出接口的动态类型和值?
package main

import (
   "fmt"
   "unsafe"
)

type iface struct {
   itab, data uintptr
}

func main() {
   var a interface{} = nil

   var b interface{} = (*int)(nil)

   x := 5
   var c interface{} = (*int)(&amp;x)

   ia := *(*iface)(unsafe.Pointer(&amp;a))
   ib := *(*iface)(unsafe.Pointer(&amp;b))
   ic := *(*iface)(unsafe.Pointer(&amp;c))

   fmt.Println(ia, ib, ic)

   fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}

输出结果

{0 0} {17361600 0} {17361600 824634322600}
5
4. 内置方法结构体的隐式调用转换会失效!
package main

import "fmt"

type Student struct {
   Name string
   Age int
}

func (s *Student) String() string {
   return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}

func (p *Student) debug() {
   fmt.Printf("I am debuging language pointer\n")
}

func main() {
   var s Student = Student{
      Name: "qcrao",
      Age: 18,
   }
   s.debug()
   fmt.Println(s)
}

这里我们看到,String()没有被调用到.对于内置类型,函数内部会用穷举法,得出它的真实类型,然后转换为字符串打印。而对于自定义类型,首先确定该类型是否实现了 String() 方法,如果实现了,则直接打印输出 String() 方法的结果;否则,会通过反射来遍历对象的成员进行打印。
输出结果

I am debuging language pointer
{qcrao 18}

参考文档

  1. 两万字深入解密 Go 语言接口的那些事儿 | 技术头条
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爽朗地狮子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值