searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Golang深入浅出——interface结构解析

2023-04-04 02:33:59
15
0

简介

interface类型在golang中是一种引用类型,多用在对一些相关或聚合类的function的一些抽象,实现类似于c++中的的多态。

类型定义

通用_type

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers    
    hash       uint32 
    tflag      tflag
    align      uint8
    fieldAlign uint8 
    kind       uint8// function for comparing objects of this type                      
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.        
    // If the KindGCProg bit is set in kind, gcdata is a GC program.    
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.    
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

空interface定义

//空interface
type eface struct {      
    _type *_type
    data  unsafe.Pointer
}

 

非空interface定义

//非空interface
type iface struct {    
    tab  *itab         
    data unsafe.Pointer
}

type itab struct {          
    inter *interfacetype       
    _type *_type                 
    hash  uint32 // copy of _type.hash. Used for switches.   _     [4]byte               
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.  
}

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

 

常用场景

interface主要有2个用途,分别是用于层与层之间进行抽象和解耦;实现伪泛型(利用空inteface作为函数或参数),是golang实现多态(itab.fun表保存函数列表首地址)和反射的基础。

类型转换

指针类型转换

示例:

package main

import "fmt"

func main() {
        var s Person = &Student{name: "test-name"}
        s.sayHello("everyone")
}

type Person interface {
        sayHello(name string) string
        sayGoodbye(name string) string
}

type Student struct {
        name string
}

//go:noinline
func (s *Student) sayHello(name string) string {
        return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}

//go:noinline
func (s *Student) sayGoodbye(name string) string {
        return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}

生成汇编

go tool compile -S -N -l main.go > main.s 2>&1

汇编代码解析

1)main函数主流程

"".main STEXT size=194 args=0x0 locals=0x50
	0x0000 00000 (main.go:5)	TEXT	"".main(SB), ABIInternal, $80-0
	0x0000 00000 (main.go:5)	MOVQ	(TLS), CX
	0x0009 00009 (main.go:5)	CMPQ	SP, 16(CX)
	0x000d 00013 (main.go:5)	PCDATA	$0, $-2
	0x000d 00013 (main.go:5)	JLS	184
	0x0013 00019 (main.go:5)	PCDATA	$0, $-1
	0x0013 00019 (main.go:5)	SUBQ	$80, SP
	0x0017 00023 (main.go:5)	MOVQ	BP, 72(SP)
	0x001c 00028 (main.go:5)	LEAQ	72(SP), BP
	0x0021 00033 (main.go:5)	PCDATA	$0, $-2
	0x0021 00033 (main.go:5)	PCDATA	$1, $-2
	0x0021 00033 (main.go:5)	FUNCDATA	$0, gclocals·7d2d5fca80364273fb07d5820a76fef4(SB)
	0x0021 00033 (main.go:5)	FUNCDATA	$1, gclocals·95a7510f9a0f8c4e1ae4a25795da4a33(SB)
	0x0021 00033 (main.go:5)	FUNCDATA	$2, gclocals·bfebb10a556cfca952c51fc0f9511921(SB)
	0x0021 00033 (main.go:6)	PCDATA	$0, $1
	0x0021 00033 (main.go:6)	PCDATA	$1, $0
	0x0021 00033 (main.go:6)	LEAQ	type."".Student(SB), AX
	0x0028 00040 (main.go:6)	PCDATA	$0, $0
	0x0028 00040 (main.go:6)	MOVQ	AX, (SP)
	0x002c 00044 (main.go:6)	CALL	runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ	AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
	0x0031 00049 (main.go:6)	PCDATA	$0, $2
	0x0031 00049 (main.go:6)	MOVQ	8(SP), DI
	0x0036 00054 (main.go:6)	PCDATA	$1, $1
	0x0036 00054 (main.go:6)	MOVQ	DI, ""..autotmp_2+40(SP)
	0x003b 00059 (main.go:6)	MOVQ	$9, 8(DI)
	0x0043 00067 (main.go:6)	PCDATA	$0, $-2
	0x0043 00067 (main.go:6)	PCDATA	$1, $-2
	0x0043 00067 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x004a 00074 (main.go:6)	JEQ	78
	0x004c 00076 (main.go:6)	JMP	170
	0x004e 00078 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0055 00085 (main.go:6)	MOVQ	AX, (DI)
	0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1
	0x005a 00090 (main.go:6)	PCDATA	$1, $0
	0x005a 00090 (main.go:6)	MOVQ	""..autotmp_2+40(SP), AX
	0x005f 00095 (main.go:6)	MOVQ	AX, ""..autotmp_1+48(SP)
	0x0064 00100 (main.go:6)	PCDATA	$0, $3
	0x0064 00100 (main.go:6)	PCDATA	$1, $2
	0x0064 00100 (main.go:6)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
	0x006b 00107 (main.go:6)	PCDATA	$0, $1
	0x006b 00107 (main.go:6)	MOVQ	CX, "".s+56(SP)
	0x0070 00112 (main.go:6)	PCDATA	$0, $0
	0x0070 00112 (main.go:6)	MOVQ	AX, "".s+64(SP)
	0x0075 00117 (main.go:7)	MOVQ	"".s+56(SP), AX
	0x007a 00122 (main.go:7)	TESTB	AL, (AX)
	0x007c 00124 (main.go:7)	MOVQ	32(AX), AX
	0x0080 00128 (main.go:7)	PCDATA	$0, $4
	0x0080 00128 (main.go:7)	PCDATA	$1, $0
	0x0080 00128 (main.go:7)	MOVQ	"".s+64(SP), CX
	0x0085 00133 (main.go:7)	PCDATA	$0, $0
	0x0085 00133 (main.go:7)	MOVQ	CX, (SP)
	0x0089 00137 (main.go:7)	PCDATA	$0, $4
	0x0089 00137 (main.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0090 00144 (main.go:7)	PCDATA	$0, $0
	0x0090 00144 (main.go:7)	MOVQ	CX, 8(SP)
	0x0095 00149 (main.go:7)	MOVQ	$8, 16(SP)
	0x009e 00158 (main.go:7)	CALL	AX
	0x00a0 00160 (main.go:8)	MOVQ	72(SP), BP
	0x00a5 00165 (main.go:8)	ADDQ	$80, SP
	0x00a9 00169 (main.go:8)	RET
	0x00aa 00170 (main.go:6)	PCDATA	$0, $-2
	0x00aa 00170 (main.go:6)	PCDATA	$1, $-2
	0x00aa 00170 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x00b1 00177 (main.go:6)	CALL	runtime.gcWriteBarrier(SB)
	0x00b6 00182 (main.go:6)	JMP	90
	0x00b8 00184 (main.go:6)	NOP
	0x00b8 00184 (main.go:5)	PCDATA	$1, $-1
	0x00b8 00184 (main.go:5)	PCDATA	$0, $-2
	0x00b8 00184 (main.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x00bd 00189 (main.go:5)	PCDATA	$0, $-1
	0x00bd 00189 (main.go:5)	JMP	0
	0x0000 64 48 8b 0c 25 00 00 00 00 48 3b 61 10 0f 86 a5  dH..%....H;a....
	0x0010 00 00 00 48 83 ec 50 48 89 6c 24 48 48 8d 6c 24  ...H..PH.l$HH.l$
	0x0020 48 48 8d 05 00 00 00 00 48 89 04 24 e8 00 00 00  HH......H..$....
	0x0030 00 48 8b 7c 24 08 48 89 7c 24 28 48 c7 47 08 09  .H.|$.H.|$(H.G..
	0x0040 00 00 00 83 3d 00 00 00 00 00 74 02 eb 5c 48 8d  ....=.....t..\H.
	0x0050 05 00 00 00 00 48 89 07 eb 00 48 8b 44 24 28 48  .....H....H.D$(H
	0x0060 89 44 24 30 48 8d 0d 00 00 00 00 48 89 4c 24 38  .D$0H......H.L$8
	0x0070 48 89 44 24 40 48 8b 44 24 38 84 00 48 8b 40 20  H.D$@H.D$8..H.@ 
	0x0080 48 8b 4c 24 40 48 89 0c 24 48 8d 0d 00 00 00 00  H.L$@H..$H......
	0x0090 48 89 4c 24 08 48 c7 44 24 10 08 00 00 00 ff d0  H.L$.H.D$.......
	0x00a0 48 8b 6c 24 48 48 83 c4 50 c3 48 8d 05 00 00 00  H.l$HH..P.H.....
	0x00b0 00 e8 00 00 00 00 eb a2 e8 00 00 00 00 e9 3e ff  ..............>.
	0x00c0 ff ff                                            ..
	rel 5+4 t=17 TLS+0
	rel 36+4 t=16 type."".Student+0
	rel 45+4 t=8 runtime.newobject+0
	rel 69+4 t=16 runtime.writeBarrier+-1
	rel 81+4 t=16 go.string."test-name"+0
	rel 103+4 t=16 go.itab.*"".Student,"".Person+0
	rel 140+4 t=16 go.string."everyone"+0
	rel 158+0 t=11 +0
	rel 173+4 t=16 go.string."test-name"+0
	rel 178+4 t=8 runtime.gcWriteBarrier+0
	rel 185+4 t=8 runtime.morestack_noctxt+0

 

2)生成Student对象
执行流程:
2.1)获取Student类型地址,放到AX寄存器,然后放到栈顶,调用runtime.newobject创建对象,放到SB中,新对象地址保存在栈顶:8(SP)

2.2)0x0031~0x003b:创建临时对象autotmp_2放到40(SP),同时将对象的长度9设置到8(DI)

2.3)0x0043:判断runtime.writeBarrier(SB)是否=0,若等于0,则跳转到==78(0x004e)执行,在栈中分配对象,否则跳转到170(0x00aa)==执行,生成GC对象;

2.4)0x004e ~ 0x0055:获取字符串“test-name”在数据区地址,放到AX再设置到DI,即设置name字段0x00aa ~ 0x00b1调用runtime.gcWriteBarrier(SB)生成GC监控对象;

0x0021 00033 (main.go:6)	LEAQ	type."".Student(SB), AX
	0x0028 00040 (main.go:6)	PCDATA	$0, $0
	0x0028 00040 (main.go:6)	MOVQ	AX, (SP)
	0x002c 00044 (main.go:6)	CALL	runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ	AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
	0x0031 00049 (main.go:6)	PCDATA	$0, $2
	0x0031 00049 (main.go:6)	MOVQ	8(SP), DI 
	0x0036 00054 (main.go:6)	PCDATA	$1, $1
	0x0036 00054 (main.go:6)	MOVQ	DI, ""..autotmp_2+40(SP)
	0x003b 00059 (main.go:6)	MOVQ	$9, 8(DI)
	0x0043 00067 (main.go:6)	PCDATA	$0, $-2
	0x0043 00067 (main.go:6)	PCDATA	$1, $-2
	0x0043 00067 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x004a 00074 (main.go:6)	JEQ	78
	0x004c 00076 (main.go:6)	JMP	170
	0x004e 00078 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0055 00085 (main.go:6)	MOVQ	AX, (DI)
	0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1

...

	0x00aa 00170 (main.go:6)	PCDATA	$0, $-2
	0x00aa 00170 (main.go:6)	PCDATA	$1, $-2
	0x00aa 00170 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x00b1 00177 (main.go:6)	CALL	runtime.gcWriteBarrier(SB)
	0x00b6 00182 (main.go:6)	JMP	90
	0x00b8 00184 (main.go:6)	NOP

 

3)对象复制

执行流程:
3.1)0x005a ~ 0x005f生成临时变量autotmp_1
3.2)0x0064 ~ 0x006b生成一个itab.*"".Student,"".Person结构,防止在56(SP)
3.3)0x0070将变量autotmp_2+40(SP)设置在itab.*"".Student,"".Person后8个字节(65(SP)),一起组成一个iface结构,类型转换;

0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1
	0x005a 00090 (main.go:6)	PCDATA	$1, $0
	0x005a 00090 (main.go:6)	MOVQ	""..autotmp_2+40(SP), AX
	0x005f 00095 (main.go:6)	MOVQ	AX, ""..autotmp_1+48(SP)
	0x0064 00100 (main.go:6)	PCDATA	$0, $3
	0x0064 00100 (main.go:6)	PCDATA	$1, $2
	0x0064 00100 (main.go:6)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
	0x006b 00107 (main.go:6)	PCDATA	$0, $1
	0x006b 00107 (main.go:6)	MOVQ	CX, "".s+56(SP)
	0x0070 00112 (main.go:6)	PCDATA	$0, $0
	0x0070 00112 (main.go:6)	MOVQ	AX, "".s+64(SP)

4)调用实例化对象Student函数
执行流程:
4.1)0x0075 ~ 0x007c将iface结构地址放置到AX寄存器,校验iface结构非空,32(AX)偏移到sayHello函数

4.2)0x0080 ~ 0x0085将*Student对象,压入栈顶 (SP)

4.3)0x0089 ~ 0x0095将字符串"everyone",压入栈顶8(SP)

4.4)0x009调用sayHello(CALL AX)函数

0x0075 00117 (main.go:7)	MOVQ	"".s+56(SP), AX
	0x007a 00122 (main.go:7)	TESTB	AL, (AX)
	0x007c 00124 (main.go:7)	MOVQ	32(AX), AX
	0x0080 00128 (main.go:7)	PCDATA	$0, $4
	0x0080 00128 (main.go:7)	PCDATA	$1, $0
	0x0080 00128 (main.go:7)	MOVQ	"".s+64(SP), CX
	0x0085 00133 (main.go:7)	PCDATA	$0, $0
	0x0085 00133 (main.go:7)	MOVQ	CX, (SP)
	0x0089 00137 (main.go:7)	PCDATA	$0, $4
	0x0089 00137 (main.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0090 00144 (main.go:7)	PCDATA	$0, $0
	0x0090 00144 (main.go:7)	MOVQ	CX, 8(SP)
	0x0095 00149 (main.go:7)	MOVQ	$8, 16(SP)
	0x009e 00158 (main.go:7)	CALL	AX
	0x00a0 00160 (main.go:8)	MOVQ	72(SP), BP
	0x00a5 00165 (main.go:8)	ADDQ	$80, SP
	0x00a9 00169 (main.go:8)	RET

 

对象类型

示例golang代码:

package main

import "fmt"

func main() {
        var s Person = Student{name: "test-name"}
        s.sayHello("everyone")
}

type Person interface {
        sayHello(name string) string
        sayGoodbye(name string) string
}

type Student struct {
        name string
}

//go:noinline
func (s Student) sayHello(name string) string {
        return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}

//go:noinline
func (s Student) sayGoodbye(name string) string {
        return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}

1)生成Student对象
栈空间64(SP) 创建临时对象autotmp_1,调用runtime.convTString,新生成的对象,存放在16(SP)

	0x0021 00033 (main_object.go:6)	PCDATA	$0, $0
	0x0021 00033 (main_object.go:6)	PCDATA	$1, $0
	0x0021 00033 (main_object.go:6)	XORPS	X0, X0
	0x0024 00036 (main_object.go:6)	MOVUPS	X0, ""..autotmp_1+64(SP)
	0x0029 00041 (main_object.go:6)	PCDATA	$0, $1
	0x0029 00041 (main_object.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0030 00048 (main_object.go:6)	MOVQ	AX, ""..autotmp_1+64(SP)
	0x0035 00053 (main_object.go:6)	MOVQ	$9, ""..autotmp_1+72(SP)
	0x003e 00062 (main_object.go:6)	PCDATA	$0, $0
	0x003e 00062 (main_object.go:6)	MOVQ	AX, (SP)
	0x0042 00066 (main_object.go:6)	MOVQ	$9, 8(SP)
	0x004b 00075 (main_object.go:6)	CALL	runtime.convTstring(SB)

2)创建临时变量autotmp_2

0x0050 00080 (main_object.go:6)	PCDATA	$0, $1
	0x0050 00080 (main_object.go:6)	MOVQ	16(SP), AX
	0x0055 00085 (main_object.go:6)	MOVQ	AX, ""..autotmp_2+40(SP)

3)创建iface结构
0x005a ~ 0x0061 生成一个go.itab."".Student,"".Person结构,并存放到48(SP)
0x0066 设置iface.data为*Student即16(SP)

	0x005a 00090 (main_object.go:6)	PCDATA	$0, $2
	0x005a 00090 (main_object.go:6)	PCDATA	$1, $1
	0x005a 00090 (main_object.go:6)	LEAQ	go.itab."".Student,"".Person(SB), CX
	0x0061 00097 (main_object.go:6)	PCDATA	$0, $1
	0x0061 00097 (main_object.go:6)	MOVQ	CX, "".s+48(SP)
	0x0066 00102 (main_object.go:6)	PCDATA	$0, $0
	0x0066 00102 (main_object.go:6)	MOVQ	AX, "".s+56(SP)

4)调用实例化对象函数
执行流程:
4.1)0x006b ~ 0x0072 偏移到Student.sayHello函数地址,压入AX寄存器

4.2)0x0076 ~ 0x007b将iface.data压入栈顶

4.3)0x007f ~ 0x008b将sayHello参数“everyone”地址和字符串长度,压入栈顶

4.4)0x0094调用sayHello

	0x006b 00107 (main_object.go:7)	MOVQ	"".s+48(SP), AX
	0x0070 00112 (main_object.go:7)	TESTB	AL, (AX)
	0x0072 00114 (main_object.go:7)	MOVQ	32(AX), AX
	0x0076 00118 (main_object.go:7)	PCDATA	$0, $3
	0x0076 00118 (main_object.go:7)	PCDATA	$1, $0
	0x0076 00118 (main_object.go:7)	MOVQ	"".s+56(SP), CX
	0x007b 00123 (main_object.go:7)	PCDATA	$0, $0
	0x007b 00123 (main_object.go:7)	MOVQ	CX, (SP)
	0x007f 00127 (main_object.go:7)	PCDATA	$0, $3
	0x007f 00127 (main_object.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0086 00134 (main_object.go:7)	PCDATA	$0, $0
	0x0086 00134 (main_object.go:7)	MOVQ	CX, 8(SP)
	0x008b 00139 (main_object.go:7)	MOVQ	$8, 16(SP)
	0x0094 00148 (main_object.go:7)	CALL	AX

类型断言

非空interface

1)断言为interface:
调用runtime.assertI2I2(SB),如果和目标interfacetype不同,则会按目标interfacetype生成一个新的interface返回
示例:

func main() {
	var s Person = &Student{name: "test-name"}
	v, ok := s.(Person)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

2)断言为结构体
类型断言时,新构造一个*itab结构(参考汇编0x0094),和interface的*itab进行对比;
示例:

func main() {
	var s Person = &Student{name: "test-name"}
	v, ok := s.(*Student)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

汇编:
0x0075 00117 (main.go:8)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
0x007c 00124 (main.go:8)	MOVQ	CX, "".s+104(SP)
0x0081 00129 (main.go:8)	MOVQ	AX, "".s+112(SP)
0x0086 00134 (main.go:9)	MOVQ	$0, ""..autotmp_3+96(SP)
0x008f 00143 (main.go:9)	MOVQ	"".s+112(SP), AX
0x0094 00148 (main.go:9)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
0x009b 00155 (main.go:9)	NOP
0x00a0 00160 (main.go:9)	CMPQ	"".s+104(SP), CX

空interface

1)空interface类型断言,用eface.*_type和目标结构体的*_type对比;

备注:0x0069 ~ 0x0075生成了一个Student对象
示例:

func main() {
	var s interface{} = &Student{name: "test-name"}
	v, ok := s.(int)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

汇编:
0x002f 00047 (main.go:8)	XORPS	X0, X0
0x0032 00050 (main.go:8)	MOVUPS	X0, ""..autotmp_8+136(SP)
0x003a 00058 (main.go:8)	LEAQ	""..autotmp_8+136(SP), AX
0x0042 00066 (main.go:8)	MOVQ	AX, ""..autotmp_7+88(SP)
0x0047 00071 (main.go:8)	TESTB	AL, (AX)
0x0049 00073 (main.go:8)	MOVQ	$9, ""..autotmp_8+144(SP)
0x0055 00085 (main.go:8)	LEAQ	go.string."test-name"(SB), CX
0x005c 00092 (main.go:8)	MOVQ	CX, ""..autotmp_8+136(SP)
0x0064 00100 (main.go:8)	MOVQ	AX, ""..autotmp_3+96(SP)
0x0069 00105 (main.go:8)	LEAQ	type.*"".Student(SB), CX
0x0070 00112 (main.go:8)	MOVQ	CX, "".s+120(SP)
0x0075 00117 (main.go:8)	MOVQ	AX, "".s+128(SP)


对比_type:
0x007d 00125 (main.go:9)	MOVQ	"".s+120(SP), AX
0x0082 00130 (main.go:9)	MOVQ	"".s+128(SP), CX
0x008a 00138 (main.go:9)	LEAQ	type.int(SB), DX
0x0091 00145 (main.go:9)	CMPQ	DX, AX
0x0094 00148 (main.go:9)	JEQ	155
0x0096 00150 (main.go:9)	JMP	423

 

类型查询(Type Switchs)

非空接口

switch参数为非空接口
示例

func main() {
	var s Person = &Student{name: "test-name"}
	switch s.(type) {
	case Person:
		person := s.(Person)
		person.sayHello("boy")
	case *Student:
		student := s.(*Student)
		student.sayHello("boy")
	case Student:
		student := s.(Student)
		student.sayHello("boy")
	}
}

1)case 非空接口类型名,调用runtime.assertI2I2(),成功,则case匹配,进入case代码段

2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段

空接口

switch参数为空接口
示例

func main() {
	var s interface{} = &Student{name: "test-name"}
	switch s.(type) {
	case Person:
		person := s.(Person)
		person.sayHello("everyone")
	case *Student:
		student := s.(*Student)
		student.sayHello("everyone")
	case Student:
		student := s.(Student)
		student.sayHello("everyone")
	}
}

func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
	t := e._type
	if t == nil {
		return
	}
	tab := getitab(inter, t, true)
	if tab == nil {
		return
	}
	r.tab = tab
	r.data = e.data
	b = true
	return
}

1)case 非空接口类型名,调用runtime.assertE2I2(),通过getitab把eface.*_type封装成*itab;封装成功,则case匹配,进入case代码段

2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段

备注:
1)fallthrough不适用于Type Switchs
2)hash值匹配只和字段和方法有关,和字段内具体值无关

性能消耗

1)构造iface过程
2)动态计算调用函数地址,通过iface.tab.fun表偏移查找;

场景对比

1)通过指针,实现多态,性能消耗很小,相对复杂业务逻辑,可忽略;
2)通过结构体,结构体在方法调用时,需要传值,拷贝参数,性能损耗较大;
建议:通过指针实现多态;

 

 

 

0条评论
0 / 1000
s****n
5文章数
0粉丝数
s****n
5 文章 | 0 粉丝
原创

Golang深入浅出——interface结构解析

2023-04-04 02:33:59
15
0

简介

interface类型在golang中是一种引用类型,多用在对一些相关或聚合类的function的一些抽象,实现类似于c++中的的多态。

类型定义

通用_type

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers    
    hash       uint32 
    tflag      tflag
    align      uint8
    fieldAlign uint8 
    kind       uint8// function for comparing objects of this type                      
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.        
    // If the KindGCProg bit is set in kind, gcdata is a GC program.    
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.    
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

空interface定义

//空interface
type eface struct {      
    _type *_type
    data  unsafe.Pointer
}

 

非空interface定义

//非空interface
type iface struct {    
    tab  *itab         
    data unsafe.Pointer
}

type itab struct {          
    inter *interfacetype       
    _type *_type                 
    hash  uint32 // copy of _type.hash. Used for switches.   _     [4]byte               
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.  
}

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

 

常用场景

interface主要有2个用途,分别是用于层与层之间进行抽象和解耦;实现伪泛型(利用空inteface作为函数或参数),是golang实现多态(itab.fun表保存函数列表首地址)和反射的基础。

类型转换

指针类型转换

示例:

package main

import "fmt"

func main() {
        var s Person = &Student{name: "test-name"}
        s.sayHello("everyone")
}

type Person interface {
        sayHello(name string) string
        sayGoodbye(name string) string
}

type Student struct {
        name string
}

//go:noinline
func (s *Student) sayHello(name string) string {
        return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}

//go:noinline
func (s *Student) sayGoodbye(name string) string {
        return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}

生成汇编

go tool compile -S -N -l main.go > main.s 2>&1

汇编代码解析

1)main函数主流程

"".main STEXT size=194 args=0x0 locals=0x50
	0x0000 00000 (main.go:5)	TEXT	"".main(SB), ABIInternal, $80-0
	0x0000 00000 (main.go:5)	MOVQ	(TLS), CX
	0x0009 00009 (main.go:5)	CMPQ	SP, 16(CX)
	0x000d 00013 (main.go:5)	PCDATA	$0, $-2
	0x000d 00013 (main.go:5)	JLS	184
	0x0013 00019 (main.go:5)	PCDATA	$0, $-1
	0x0013 00019 (main.go:5)	SUBQ	$80, SP
	0x0017 00023 (main.go:5)	MOVQ	BP, 72(SP)
	0x001c 00028 (main.go:5)	LEAQ	72(SP), BP
	0x0021 00033 (main.go:5)	PCDATA	$0, $-2
	0x0021 00033 (main.go:5)	PCDATA	$1, $-2
	0x0021 00033 (main.go:5)	FUNCDATA	$0, gclocals·7d2d5fca80364273fb07d5820a76fef4(SB)
	0x0021 00033 (main.go:5)	FUNCDATA	$1, gclocals·95a7510f9a0f8c4e1ae4a25795da4a33(SB)
	0x0021 00033 (main.go:5)	FUNCDATA	$2, gclocals·bfebb10a556cfca952c51fc0f9511921(SB)
	0x0021 00033 (main.go:6)	PCDATA	$0, $1
	0x0021 00033 (main.go:6)	PCDATA	$1, $0
	0x0021 00033 (main.go:6)	LEAQ	type."".Student(SB), AX
	0x0028 00040 (main.go:6)	PCDATA	$0, $0
	0x0028 00040 (main.go:6)	MOVQ	AX, (SP)
	0x002c 00044 (main.go:6)	CALL	runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ	AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
	0x0031 00049 (main.go:6)	PCDATA	$0, $2
	0x0031 00049 (main.go:6)	MOVQ	8(SP), DI
	0x0036 00054 (main.go:6)	PCDATA	$1, $1
	0x0036 00054 (main.go:6)	MOVQ	DI, ""..autotmp_2+40(SP)
	0x003b 00059 (main.go:6)	MOVQ	$9, 8(DI)
	0x0043 00067 (main.go:6)	PCDATA	$0, $-2
	0x0043 00067 (main.go:6)	PCDATA	$1, $-2
	0x0043 00067 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x004a 00074 (main.go:6)	JEQ	78
	0x004c 00076 (main.go:6)	JMP	170
	0x004e 00078 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0055 00085 (main.go:6)	MOVQ	AX, (DI)
	0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1
	0x005a 00090 (main.go:6)	PCDATA	$1, $0
	0x005a 00090 (main.go:6)	MOVQ	""..autotmp_2+40(SP), AX
	0x005f 00095 (main.go:6)	MOVQ	AX, ""..autotmp_1+48(SP)
	0x0064 00100 (main.go:6)	PCDATA	$0, $3
	0x0064 00100 (main.go:6)	PCDATA	$1, $2
	0x0064 00100 (main.go:6)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
	0x006b 00107 (main.go:6)	PCDATA	$0, $1
	0x006b 00107 (main.go:6)	MOVQ	CX, "".s+56(SP)
	0x0070 00112 (main.go:6)	PCDATA	$0, $0
	0x0070 00112 (main.go:6)	MOVQ	AX, "".s+64(SP)
	0x0075 00117 (main.go:7)	MOVQ	"".s+56(SP), AX
	0x007a 00122 (main.go:7)	TESTB	AL, (AX)
	0x007c 00124 (main.go:7)	MOVQ	32(AX), AX
	0x0080 00128 (main.go:7)	PCDATA	$0, $4
	0x0080 00128 (main.go:7)	PCDATA	$1, $0
	0x0080 00128 (main.go:7)	MOVQ	"".s+64(SP), CX
	0x0085 00133 (main.go:7)	PCDATA	$0, $0
	0x0085 00133 (main.go:7)	MOVQ	CX, (SP)
	0x0089 00137 (main.go:7)	PCDATA	$0, $4
	0x0089 00137 (main.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0090 00144 (main.go:7)	PCDATA	$0, $0
	0x0090 00144 (main.go:7)	MOVQ	CX, 8(SP)
	0x0095 00149 (main.go:7)	MOVQ	$8, 16(SP)
	0x009e 00158 (main.go:7)	CALL	AX
	0x00a0 00160 (main.go:8)	MOVQ	72(SP), BP
	0x00a5 00165 (main.go:8)	ADDQ	$80, SP
	0x00a9 00169 (main.go:8)	RET
	0x00aa 00170 (main.go:6)	PCDATA	$0, $-2
	0x00aa 00170 (main.go:6)	PCDATA	$1, $-2
	0x00aa 00170 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x00b1 00177 (main.go:6)	CALL	runtime.gcWriteBarrier(SB)
	0x00b6 00182 (main.go:6)	JMP	90
	0x00b8 00184 (main.go:6)	NOP
	0x00b8 00184 (main.go:5)	PCDATA	$1, $-1
	0x00b8 00184 (main.go:5)	PCDATA	$0, $-2
	0x00b8 00184 (main.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x00bd 00189 (main.go:5)	PCDATA	$0, $-1
	0x00bd 00189 (main.go:5)	JMP	0
	0x0000 64 48 8b 0c 25 00 00 00 00 48 3b 61 10 0f 86 a5  dH..%....H;a....
	0x0010 00 00 00 48 83 ec 50 48 89 6c 24 48 48 8d 6c 24  ...H..PH.l$HH.l$
	0x0020 48 48 8d 05 00 00 00 00 48 89 04 24 e8 00 00 00  HH......H..$....
	0x0030 00 48 8b 7c 24 08 48 89 7c 24 28 48 c7 47 08 09  .H.|$.H.|$(H.G..
	0x0040 00 00 00 83 3d 00 00 00 00 00 74 02 eb 5c 48 8d  ....=.....t..\H.
	0x0050 05 00 00 00 00 48 89 07 eb 00 48 8b 44 24 28 48  .....H....H.D$(H
	0x0060 89 44 24 30 48 8d 0d 00 00 00 00 48 89 4c 24 38  .D$0H......H.L$8
	0x0070 48 89 44 24 40 48 8b 44 24 38 84 00 48 8b 40 20  H.D$@H.D$8..H.@ 
	0x0080 48 8b 4c 24 40 48 89 0c 24 48 8d 0d 00 00 00 00  H.L$@H..$H......
	0x0090 48 89 4c 24 08 48 c7 44 24 10 08 00 00 00 ff d0  H.L$.H.D$.......
	0x00a0 48 8b 6c 24 48 48 83 c4 50 c3 48 8d 05 00 00 00  H.l$HH..P.H.....
	0x00b0 00 e8 00 00 00 00 eb a2 e8 00 00 00 00 e9 3e ff  ..............>.
	0x00c0 ff ff                                            ..
	rel 5+4 t=17 TLS+0
	rel 36+4 t=16 type."".Student+0
	rel 45+4 t=8 runtime.newobject+0
	rel 69+4 t=16 runtime.writeBarrier+-1
	rel 81+4 t=16 go.string."test-name"+0
	rel 103+4 t=16 go.itab.*"".Student,"".Person+0
	rel 140+4 t=16 go.string."everyone"+0
	rel 158+0 t=11 +0
	rel 173+4 t=16 go.string."test-name"+0
	rel 178+4 t=8 runtime.gcWriteBarrier+0
	rel 185+4 t=8 runtime.morestack_noctxt+0

 

2)生成Student对象
执行流程:
2.1)获取Student类型地址,放到AX寄存器,然后放到栈顶,调用runtime.newobject创建对象,放到SB中,新对象地址保存在栈顶:8(SP)

2.2)0x0031~0x003b:创建临时对象autotmp_2放到40(SP),同时将对象的长度9设置到8(DI)

2.3)0x0043:判断runtime.writeBarrier(SB)是否=0,若等于0,则跳转到==78(0x004e)执行,在栈中分配对象,否则跳转到170(0x00aa)==执行,生成GC对象;

2.4)0x004e ~ 0x0055:获取字符串“test-name”在数据区地址,放到AX再设置到DI,即设置name字段0x00aa ~ 0x00b1调用runtime.gcWriteBarrier(SB)生成GC监控对象;

0x0021 00033 (main.go:6)	LEAQ	type."".Student(SB), AX
	0x0028 00040 (main.go:6)	PCDATA	$0, $0
	0x0028 00040 (main.go:6)	MOVQ	AX, (SP)
	0x002c 00044 (main.go:6)	CALL	runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ	AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
	0x0031 00049 (main.go:6)	PCDATA	$0, $2
	0x0031 00049 (main.go:6)	MOVQ	8(SP), DI 
	0x0036 00054 (main.go:6)	PCDATA	$1, $1
	0x0036 00054 (main.go:6)	MOVQ	DI, ""..autotmp_2+40(SP)
	0x003b 00059 (main.go:6)	MOVQ	$9, 8(DI)
	0x0043 00067 (main.go:6)	PCDATA	$0, $-2
	0x0043 00067 (main.go:6)	PCDATA	$1, $-2
	0x0043 00067 (main.go:6)	CMPL	runtime.writeBarrier(SB), $0
	0x004a 00074 (main.go:6)	JEQ	78
	0x004c 00076 (main.go:6)	JMP	170
	0x004e 00078 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0055 00085 (main.go:6)	MOVQ	AX, (DI)
	0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1

...

	0x00aa 00170 (main.go:6)	PCDATA	$0, $-2
	0x00aa 00170 (main.go:6)	PCDATA	$1, $-2
	0x00aa 00170 (main.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x00b1 00177 (main.go:6)	CALL	runtime.gcWriteBarrier(SB)
	0x00b6 00182 (main.go:6)	JMP	90
	0x00b8 00184 (main.go:6)	NOP

 

3)对象复制

执行流程:
3.1)0x005a ~ 0x005f生成临时变量autotmp_1
3.2)0x0064 ~ 0x006b生成一个itab.*"".Student,"".Person结构,防止在56(SP)
3.3)0x0070将变量autotmp_2+40(SP)设置在itab.*"".Student,"".Person后8个字节(65(SP)),一起组成一个iface结构,类型转换;

0x0058 00088 (main.go:6)	JMP	90
	0x005a 00090 (main.go:6)	PCDATA	$0, $1
	0x005a 00090 (main.go:6)	PCDATA	$1, $0
	0x005a 00090 (main.go:6)	MOVQ	""..autotmp_2+40(SP), AX
	0x005f 00095 (main.go:6)	MOVQ	AX, ""..autotmp_1+48(SP)
	0x0064 00100 (main.go:6)	PCDATA	$0, $3
	0x0064 00100 (main.go:6)	PCDATA	$1, $2
	0x0064 00100 (main.go:6)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
	0x006b 00107 (main.go:6)	PCDATA	$0, $1
	0x006b 00107 (main.go:6)	MOVQ	CX, "".s+56(SP)
	0x0070 00112 (main.go:6)	PCDATA	$0, $0
	0x0070 00112 (main.go:6)	MOVQ	AX, "".s+64(SP)

4)调用实例化对象Student函数
执行流程:
4.1)0x0075 ~ 0x007c将iface结构地址放置到AX寄存器,校验iface结构非空,32(AX)偏移到sayHello函数

4.2)0x0080 ~ 0x0085将*Student对象,压入栈顶 (SP)

4.3)0x0089 ~ 0x0095将字符串"everyone",压入栈顶8(SP)

4.4)0x009调用sayHello(CALL AX)函数

0x0075 00117 (main.go:7)	MOVQ	"".s+56(SP), AX
	0x007a 00122 (main.go:7)	TESTB	AL, (AX)
	0x007c 00124 (main.go:7)	MOVQ	32(AX), AX
	0x0080 00128 (main.go:7)	PCDATA	$0, $4
	0x0080 00128 (main.go:7)	PCDATA	$1, $0
	0x0080 00128 (main.go:7)	MOVQ	"".s+64(SP), CX
	0x0085 00133 (main.go:7)	PCDATA	$0, $0
	0x0085 00133 (main.go:7)	MOVQ	CX, (SP)
	0x0089 00137 (main.go:7)	PCDATA	$0, $4
	0x0089 00137 (main.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0090 00144 (main.go:7)	PCDATA	$0, $0
	0x0090 00144 (main.go:7)	MOVQ	CX, 8(SP)
	0x0095 00149 (main.go:7)	MOVQ	$8, 16(SP)
	0x009e 00158 (main.go:7)	CALL	AX
	0x00a0 00160 (main.go:8)	MOVQ	72(SP), BP
	0x00a5 00165 (main.go:8)	ADDQ	$80, SP
	0x00a9 00169 (main.go:8)	RET

 

对象类型

示例golang代码:

package main

import "fmt"

func main() {
        var s Person = Student{name: "test-name"}
        s.sayHello("everyone")
}

type Person interface {
        sayHello(name string) string
        sayGoodbye(name string) string
}

type Student struct {
        name string
}

//go:noinline
func (s Student) sayHello(name string) string {
        return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}

//go:noinline
func (s Student) sayGoodbye(name string) string {
        return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}

1)生成Student对象
栈空间64(SP) 创建临时对象autotmp_1,调用runtime.convTString,新生成的对象,存放在16(SP)

	0x0021 00033 (main_object.go:6)	PCDATA	$0, $0
	0x0021 00033 (main_object.go:6)	PCDATA	$1, $0
	0x0021 00033 (main_object.go:6)	XORPS	X0, X0
	0x0024 00036 (main_object.go:6)	MOVUPS	X0, ""..autotmp_1+64(SP)
	0x0029 00041 (main_object.go:6)	PCDATA	$0, $1
	0x0029 00041 (main_object.go:6)	LEAQ	go.string."test-name"(SB), AX
	0x0030 00048 (main_object.go:6)	MOVQ	AX, ""..autotmp_1+64(SP)
	0x0035 00053 (main_object.go:6)	MOVQ	$9, ""..autotmp_1+72(SP)
	0x003e 00062 (main_object.go:6)	PCDATA	$0, $0
	0x003e 00062 (main_object.go:6)	MOVQ	AX, (SP)
	0x0042 00066 (main_object.go:6)	MOVQ	$9, 8(SP)
	0x004b 00075 (main_object.go:6)	CALL	runtime.convTstring(SB)

2)创建临时变量autotmp_2

0x0050 00080 (main_object.go:6)	PCDATA	$0, $1
	0x0050 00080 (main_object.go:6)	MOVQ	16(SP), AX
	0x0055 00085 (main_object.go:6)	MOVQ	AX, ""..autotmp_2+40(SP)

3)创建iface结构
0x005a ~ 0x0061 生成一个go.itab."".Student,"".Person结构,并存放到48(SP)
0x0066 设置iface.data为*Student即16(SP)

	0x005a 00090 (main_object.go:6)	PCDATA	$0, $2
	0x005a 00090 (main_object.go:6)	PCDATA	$1, $1
	0x005a 00090 (main_object.go:6)	LEAQ	go.itab."".Student,"".Person(SB), CX
	0x0061 00097 (main_object.go:6)	PCDATA	$0, $1
	0x0061 00097 (main_object.go:6)	MOVQ	CX, "".s+48(SP)
	0x0066 00102 (main_object.go:6)	PCDATA	$0, $0
	0x0066 00102 (main_object.go:6)	MOVQ	AX, "".s+56(SP)

4)调用实例化对象函数
执行流程:
4.1)0x006b ~ 0x0072 偏移到Student.sayHello函数地址,压入AX寄存器

4.2)0x0076 ~ 0x007b将iface.data压入栈顶

4.3)0x007f ~ 0x008b将sayHello参数“everyone”地址和字符串长度,压入栈顶

4.4)0x0094调用sayHello

	0x006b 00107 (main_object.go:7)	MOVQ	"".s+48(SP), AX
	0x0070 00112 (main_object.go:7)	TESTB	AL, (AX)
	0x0072 00114 (main_object.go:7)	MOVQ	32(AX), AX
	0x0076 00118 (main_object.go:7)	PCDATA	$0, $3
	0x0076 00118 (main_object.go:7)	PCDATA	$1, $0
	0x0076 00118 (main_object.go:7)	MOVQ	"".s+56(SP), CX
	0x007b 00123 (main_object.go:7)	PCDATA	$0, $0
	0x007b 00123 (main_object.go:7)	MOVQ	CX, (SP)
	0x007f 00127 (main_object.go:7)	PCDATA	$0, $3
	0x007f 00127 (main_object.go:7)	LEAQ	go.string."everyone"(SB), CX
	0x0086 00134 (main_object.go:7)	PCDATA	$0, $0
	0x0086 00134 (main_object.go:7)	MOVQ	CX, 8(SP)
	0x008b 00139 (main_object.go:7)	MOVQ	$8, 16(SP)
	0x0094 00148 (main_object.go:7)	CALL	AX

类型断言

非空interface

1)断言为interface:
调用runtime.assertI2I2(SB),如果和目标interfacetype不同,则会按目标interfacetype生成一个新的interface返回
示例:

func main() {
	var s Person = &Student{name: "test-name"}
	v, ok := s.(Person)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

2)断言为结构体
类型断言时,新构造一个*itab结构(参考汇编0x0094),和interface的*itab进行对比;
示例:

func main() {
	var s Person = &Student{name: "test-name"}
	v, ok := s.(*Student)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

汇编:
0x0075 00117 (main.go:8)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
0x007c 00124 (main.go:8)	MOVQ	CX, "".s+104(SP)
0x0081 00129 (main.go:8)	MOVQ	AX, "".s+112(SP)
0x0086 00134 (main.go:9)	MOVQ	$0, ""..autotmp_3+96(SP)
0x008f 00143 (main.go:9)	MOVQ	"".s+112(SP), AX
0x0094 00148 (main.go:9)	LEAQ	go.itab.*"".Student,"".Person(SB), CX
0x009b 00155 (main.go:9)	NOP
0x00a0 00160 (main.go:9)	CMPQ	"".s+104(SP), CX

空interface

1)空interface类型断言,用eface.*_type和目标结构体的*_type对比;

备注:0x0069 ~ 0x0075生成了一个Student对象
示例:

func main() {
	var s interface{} = &Student{name: "test-name"}
	v, ok := s.(int)
	if !ok {
		fmt.Printf("%v\n", v)
	}
}

汇编:
0x002f 00047 (main.go:8)	XORPS	X0, X0
0x0032 00050 (main.go:8)	MOVUPS	X0, ""..autotmp_8+136(SP)
0x003a 00058 (main.go:8)	LEAQ	""..autotmp_8+136(SP), AX
0x0042 00066 (main.go:8)	MOVQ	AX, ""..autotmp_7+88(SP)
0x0047 00071 (main.go:8)	TESTB	AL, (AX)
0x0049 00073 (main.go:8)	MOVQ	$9, ""..autotmp_8+144(SP)
0x0055 00085 (main.go:8)	LEAQ	go.string."test-name"(SB), CX
0x005c 00092 (main.go:8)	MOVQ	CX, ""..autotmp_8+136(SP)
0x0064 00100 (main.go:8)	MOVQ	AX, ""..autotmp_3+96(SP)
0x0069 00105 (main.go:8)	LEAQ	type.*"".Student(SB), CX
0x0070 00112 (main.go:8)	MOVQ	CX, "".s+120(SP)
0x0075 00117 (main.go:8)	MOVQ	AX, "".s+128(SP)


对比_type:
0x007d 00125 (main.go:9)	MOVQ	"".s+120(SP), AX
0x0082 00130 (main.go:9)	MOVQ	"".s+128(SP), CX
0x008a 00138 (main.go:9)	LEAQ	type.int(SB), DX
0x0091 00145 (main.go:9)	CMPQ	DX, AX
0x0094 00148 (main.go:9)	JEQ	155
0x0096 00150 (main.go:9)	JMP	423

 

类型查询(Type Switchs)

非空接口

switch参数为非空接口
示例

func main() {
	var s Person = &Student{name: "test-name"}
	switch s.(type) {
	case Person:
		person := s.(Person)
		person.sayHello("boy")
	case *Student:
		student := s.(*Student)
		student.sayHello("boy")
	case Student:
		student := s.(Student)
		student.sayHello("boy")
	}
}

1)case 非空接口类型名,调用runtime.assertI2I2(),成功,则case匹配,进入case代码段

2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段

空接口

switch参数为空接口
示例

func main() {
	var s interface{} = &Student{name: "test-name"}
	switch s.(type) {
	case Person:
		person := s.(Person)
		person.sayHello("everyone")
	case *Student:
		student := s.(*Student)
		student.sayHello("everyone")
	case Student:
		student := s.(Student)
		student.sayHello("everyone")
	}
}

func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
	t := e._type
	if t == nil {
		return
	}
	tab := getitab(inter, t, true)
	if tab == nil {
		return
	}
	r.tab = tab
	r.data = e.data
	b = true
	return
}

1)case 非空接口类型名,调用runtime.assertE2I2(),通过getitab把eface.*_type封装成*itab;封装成功,则case匹配,进入case代码段

2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段

备注:
1)fallthrough不适用于Type Switchs
2)hash值匹配只和字段和方法有关,和字段内具体值无关

性能消耗

1)构造iface过程
2)动态计算调用函数地址,通过iface.tab.fun表偏移查找;

场景对比

1)通过指针,实现多态,性能消耗很小,相对复杂业务逻辑,可忽略;
2)通过结构体,结构体在方法调用时,需要传值,拷贝参数,性能损耗较大;
建议:通过指针实现多态;

 

 

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0