ch5-interface

接口的底层实现

两者都是描述interfce的结构体,iface表示非空接口,eface(empty interface)表空接口

type iface struct{
	tab *itab 
	data  unsafe.Pointer 
}
type eface struct{
	_type *_type //实现接口的实体类型的对齐方式、大小等
	date unsafe.Pointer
}

两者都具有data字段,data指向接口具体的值,值一般在堆内存。不同的是iface具有itab(其中也有*_type),看看itab

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 // 保存实体类型实现的方法函数地址
}

fun数组的大小为1,其他方法在添加在第一个方法之后,增加地址大小获得之后对应方法,方法的地址是按照字典序在内存排序的。还有如果接口被重新赋值,对应的fun数组也会更新。再看看interfacetype类型

type interfacetype struct {
	typ     _type 
	pkgpath namep //接口的包名
	mhdr    []imethod //接口函数列表
}

Go每种类型都包含_type,都是在其之上增加一些字段。

值接收者和指针接收者

首先,补充方法和函数的区别,简单的说就是方法前面多了一个有类型的括号

func (a *A)Say() //方法
func Say(a *A)() //函数

而指针接口者和值接收者区别就是,方法的接收者是指针还是非指针

func (a *A)Say() //指针接收者
func (a A)Say() //值接收者

在指针接收者方法中,a的字段值可以被改变。值接收者不可以,因为传递的是值,而指针接收者传递的是指针值

实现值接收相当于自动实现指针接收,反之不然

type Coder interface{
	func Run()
	func Code()
}

type A struct{
}

func (a A)Run(){
	return
}

func (a A)Code(){
	return
}

int main(){
 var c Coder	
 ap:=&A{}
 c = ap  //正确
 ap.Run() //正确
}

但如果上述实现的函数式指针接收者,main函数改成

int main(){
 var c Coder	
 a:=A{}
 c = a  //panic
 a.Run() //panic
}

这两种接收方式如何确定,结构体比较大或者内存中只存储一份(File)选指针,是要修改字段值或者结构体式Go的内置类型选值。像map、slice等这些类型,创建的时,实际上是创建了header,而header就是为复制而生

接口实现转化的原理

type coder interface {
	code()
	run()
}

type runner interface {
	run()
}

type Gopher struct {
	language string
}

func (g Gopher) code() {
	return
}

func (g Gopher) run() {
	return
}

func main() {
	var c coder = Gopher{}

	var r runner
	r = c //接口转化
	fmt.Println(c, r)
}

在接口转化的过程中调用下面的函数,inter可以看做是r的接口类型,i是c的接口,r是新生成的接口

func convI2I(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil { 
		return
	}
	if tab.inter == inter { //如果转化双方的接口类型相同,直接赋值
		r.tab = tab
		r.data = i.data
		return
	}
	r.tab = getitab(inter, tab._type, false)
	r.data = i.data
	return
}

看看gititab

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	// ……

    // 根据 inter, typ 计算出 hash 值
	h := itabhash(inter, typ)

	// 找*tab两次第二次上锁
	var m *itab
	var locked int
	for locked = 0; locked < 2; locked++ {
		if locked != 0 {
			lock(&ifaceLock)
    }
        
        // 遍历哈希表的一个 slot
		for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {

            // 如果在 hash 表中已经找到了 itab(inter 和 typ 指针都相同)
			if m.inter == inter && m._type == typ {
                // ……
                
				if locked != 0 {
					unlock(&ifaceLock)
				}
				return m
			}
		}
	}

    // 在 hash 表中没有找到 itab,那么新生成一个 itab
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
    m._type = typ
    
    // 添加到全局的 hash 表中
	additab(m, true, canfail)
	unlock(&ifaceLock)
	if m.bad {
		return nil
	}
	return m
}

遍历两次,第一次不加锁,第二次加锁是因为要自己创建一个*tab,为了防止另外一个协程重复创建,创建之后在遇到这种转化直接从hash中找就行

再看看additab

func additab(m *itab, locked, canfail bool) {
	inter := m.inter
	typ := m._type
	x := typ.uncommon()

  //判断typ是否覆盖了inter的方法
	ni := len(inter.mhdr)
	nt := int(x.mcount)
	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
	j := 0
	for k := 0; k < ni; k++ {
		i := &inter.mhdr[k]
		itype := inter.typ.typeOff(i.ityp)
		name := inter.typ.nameOff(i.name)
		iname := name.name()
		ipkg := name.pkgPath()
		if ipkg == "" {
			ipkg = inter.pkgpath.name()
		}
		for ; j < nt; j++ {
			t := &xmhdr[j]
            tname := typ.nameOff(t.name)
            // 检查方法名字是否一致
			if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
				pkgPath := tname.pkgPath()
				if pkgPath == "" {
					pkgPath = typ.nameOff(x.pkgpath).name()
				}
				if tname.isExported() || pkgPath == ipkg {
					if m != nil {
                        // 获取函数地址,并加入到itab.fun数组中
						ifn := typ.textOff(t.ifn)
						*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
					}
					goto nextimethod
				}
			}
		}
        // ……
        
		m.bad = true
		break
	nextimethod:
	}
	if !locked {
		throw("invalid itab locking")
    }

    // 计算 hash 值
    h := itabhash(inter, typ)
    // 加到Hash Slot链表中
	m.link = hash[h]
	m.inhash = true
	atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
}

由于inter 和 typ 的方法都按方法名称进行了排序 , 只用循环 O(ni+nt),而非 O(ni*nt)

上面介绍了接口之间的转化,还有其他的转化

  1. 具体类型→接口:_type赋值为源_type,分配内存,之后赋值到新内存,data指向新内存
  2. 具体→非空:新结构体的itab直接指向入参itab,分配内存,之后赋值到新内存,data指向新内存

类型转化和断言

断言也就是对interface{}进行类型判断

var a int = 4
var i interface{} = a
_;ok := i.(int) // 也可不带ok,但会出现panic
if ok{
	//todo
}

还有一种方式

switch v:=v.(type){
case int:
//
case string:
//
default:
//
}

注意v.(type)只能是v是interce,否则编译不通过。还有利用fmt.Println()打印interface,如果是内置类型会枚举,找到正确的类型打印,对于自定义先看是否实现String(),实现调String,否则反射打印成员,实现String()时注意不要写成递归,会死循环的

func (a A)String()string {
	return fmt.Sprintf(%v,a) //死循环
}

补充一个关键字的知识点 fallthrough 常用于switch-case中 当前成立直接执行下一个

case 1:
//todo
fallthrough:
case 2:
//todo
case3:
//todo
default:
//todo

当case1成立时会,执行下一个情况里的todo,无论case 2是否成立

还有一种常见的方式判断变量是否实现某接口

var _ Coder = (*A)(nil) //判断*A是否实现Coder
var _ Coder = A{} //判断 A

为了方便,直接黏贴Go 程序员面试笔试宝典 | Go 程序员面试笔试宝典 (golang.design)部分代码注释

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值