前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GO系列(2)-interface特性

GO系列(2)-interface特性

原创
作者头像
爽朗地狮子
发布2022-10-20 11:32:27
2530
发布2022-10-20 11:32:27
举报
文章被收录于专栏:云原生系列

一. interface与struct的调用区别

struct特性:

说明struct,在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法

interface不行,必须严格按照特性,比如

例子一. 不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。
代码语言:txt
复制
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())
}

输出结果:

代码语言:txt
复制
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(), 因为自动生成的话会影响调用者。

代码语言:txt
复制
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行,如果不写&,那么会出现错误

image.png
image.png

二. iface 和 eface

1. 两者区别是什么

iface 和 eface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}

查看源码文件

/usr/local/Cellar/go@1.17/1.17.10/libexec/src/runtime/runtime2.go

代码语言:txt
复制
type iface struct {
   tab  *itab
   data unsafe.Pointer
}

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

其中iface中的itab是指向

代码语言:txt
复制
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。

image.png
image.png
2. 查看汇编代码

go tool compile -S test03.go

image.png
image.png

三. 接口判空以及打印

1. 接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil
代码语言:txt
复制
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)
}

输出结果

代码语言:txt
复制
true
c: <nil>, <nil>
true
g: *main.Gopher, <nil>
====================
false
c: *main.Gopher, <nil>
2. 结构题隐式转换的判断
代码语言:txt
复制
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
}

打印结果

代码语言:txt
复制
<nil>
false
3. 如何打印出接口的动态类型和值?
代码语言:txt
复制
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)))
}

输出结果

代码语言:txt
复制
{0 0} {17361600 0} {17361600 824634322600}
5
4. 内置方法结构体的隐式调用转换会失效!
代码语言:txt
复制
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() 方法的结果;否则,会通过反射来遍历对象的成员进行打印。

输出结果

代码语言:txt
复制
I am debuging language pointer
{qcrao 18}

参考文档

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. interface与struct的调用区别
    • 例子一. 不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。
      • 例子二. 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法
      • 二. iface 和 eface
        • 1. 两者区别是什么
          • 2. 查看汇编代码
          • 三. 接口判空以及打印
            • 1. 接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil
              • 2. 结构题隐式转换的判断
                • 3. 如何打印出接口的动态类型和值?
                  • 4. 内置方法结构体的隐式调用转换会失效!
                  • 参考文档
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档