其他分享
首页 > 其他分享> > Go底层 - 反射与interface②

Go底层 - 反射与interface②

作者:互联网

深度解密Go语言之关于 interface 的 10 个问题(一)

文章目录

Go 语言与鸭子类型的关系

先直接来看维基百科里的定义:

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

例如,在动态语言 python 中,定义一个这样的函数:

def hello_world(coder):
    coder.say_hello()
package main

import "fmt"

// 先定义一个接口,和使用此接口作为参数的函数:
type IGreeting interface {
	sayHello()
}

func sayHello(i IGreeting) {
	i.sayHello()
}

// 再来定义两个结构体:
type Go struct {
}

func (g Go) sayHello() {
	fmt.Println("Hi, I am Go")
}

type PHP struct {
}

func (p PHP) sayHello() {
	fmt.Println("Hi, I am PHP!")
}

func main() {
	golang := Go{}
	php := PHP{}
	
	sayHello(golang)
	sayHello(php)
}
```
type IGreeting interface {
	sayHello()
}

func sayHello(i IGreeting) {
	i.sayHello()
}

type Go struct {
}

func (g Go) sayHello() {
	fmt.Println("Hi, I am Go")
}

type PHP struct {
}

func (p PHP) sayHello() {
	fmt.Println("Hi, I am PHP!")
}

// 最后,在 main 函数里调用 sayHello() 函数:
func main() {
	golang := Go{}
	php := PHP{}
	
	sayHello(golang)
	sayHello(php)
}

// 程序输出:
Hi, I am GO!
Hi, I am PHP!

顺带再提一下动态语言的特点:

变量绑定的类型是不确定的

在运行期间才能确定

函数和方法可以接收任何类型的参数

调用时不检查参数类型

不需要实现接口

值接收者和指针接收者的区别

方法

来看个例子:

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())

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

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

	// ----------------------
}

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

值接收者和指针接收者

来看一个例子,就会完全明白:

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\n", p.language)
}

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

// 上述代码里定义了一个接口 coder,接口定义了两个函数:
code()
debug()

// 接着定义了一个结构体 Gopher,它实现了两个方法,一个值接收者,一个指针接收者。
// 最后,我们在 main 函数里通过接口类型的变量调用了定义的两个函数
// 运行一下,结果:
I am coding Go language
I am debuging Go language

// 但是如果我们把 main 函数的第一条语句换一下:
func main() {
    var c coder = Gopher{"Go"}
    c.code()
    c.debug()
}

// 运行一下,报错:
./main.go:24:6: cannot use Programmer literal (type Programmer) as type coder in assignment:
    Programmer does not implement coder (debug method has pointer receiver)

最后,只要记住下面这点就可以了:

如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法

两者分别在何时使用

iface 和 eface 的区别是什么

从源码层面看一下:

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

type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    hash   uint32 // copy of _type.hash. Used for type switches.
    bad    bool   // type does not implement interface
    inhash bool   // has this itab been added to hash?
    unused [2]byte
    fun    [1]uintptr // variable sized
}

再看一下 interfacetype 类型,它描述的是接口的类型:

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}

这里通过一张图来看下 iface 结构体的全貌:

在这里插入图片描述

接着来看一下 eface 的源码:

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

我们来看个例子:

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\n", p.language)
}

func main() {
	x := 200
	var any interface{} = x
	fmt.Println(any)

	g := Gopher{"Go"}
	var c coder = g
	fmt.Println(c)
}

// 执行命令,打印出汇编语言:
go tool compile -S ./src/main.go

// 可以看到,main 函数里调用了两个函数:
func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)
type _type struct {
    // 类型大小
    size       uintptr
    ptrdata    uintptr
    // 类型的 hash 值
    hash       uint32
    // 类型的 flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldalign uint8
    // 类型的编号,有bool, slice, struct 等等等等
    kind       uint8
    alg        *typeAlg
    // gc 相关
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type arraytype struct {
    typ   _type
    elem  *_type
    slice *_type
    len   uintptr
}

type chantype struct {
    typ  _type
    elem *_type
    dir  uintptr
}

type slicetype struct {
    typ  _type
    elem *_type
}

type structtype struct {
    typ     _type
    pkgPath name
    fields  []structfield
}

接口的动态类型和动态值

【引申1】接口类型和 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)

    c = g
    fmt.Println(c == nil)
    fmt.Printf("c: %T, %v\n", c, c)
}

// 输出:
true
c: <nil>, <nil>
true
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 (
    "unsafe"
    "fmt"
)

type iface struct {
    itab, data uintptr
}

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

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

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

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

    fmt.Println(ia, ib, ic)

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

// 运行结果如下:
{0 0} {17426912 0} {17426912 842350714568}
5

编译器自动检测类型是否实现接口

经常看到一些开源库里会有一些类似下面这种奇怪的用法:

var _ io.Writer = (*myWriter)(nil)

来看一个例子:

package main

import "io"

type myWriter struct {

}

/*func (w myWriter) Write(p []byte) (n int, err error) {
    return
}*/

func main() {
    // 检查 *myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = (*myWriter)(nil)

    // 检查 myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = myWriter{}
}

// 注释掉为 myWriter 定义的 Write 函数后,运行程序:
src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:
    *myWriter does not implement io.Writer (missing Write method)
src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:
    myWriter does not implement io.Writer (missing Write method)

总结一下,可通过在代码中添加类似如下的代码,用来检测类型是否实现了接口:

var _ io.Writer = (*myWriter)(nil)
var _ io.Writer = myWriter{}

标签:fmt,接收者,接口,interface,类型,Go,指针,type,底层
来源: https://blog.csdn.net/wys74230859/article/details/122030990