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 }
type interfacetype struct { typ _type pkgpath name mhdr []imethod }
inter 为 interfacetype类型指针,它包装了 _type 类型,_type 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 mhdr 字段,表示接口所定义的函数列表, pkgpath 记录定义了接口的包名。 描述了接口的类型.
fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。 这里需要注意的是 fun 是一个长度为1的数组, 可能会疑惑接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针
func ValueAndPointersDiff() { //var docker person = &Man{name: "zhangsan"} var docker person = Man{name: "zhangsan"} docker.get() docker.set("docker") fmt.Println(docker.get()) }
===== 报错如下: cannot useMan{…} (value of typeMan) as person value in variable declaration: Man does not implement person (method set has pointer receiver)
报错 Man 没有实现 person。 而两者的区别则是第一次将 &Man 赋给了 docker, 第二次将 Man 赋给了 docker。 这也能验证我们上面说的那个结论: 虽然 *Man 没有实现 get 方法, 但是 Man 实现了 get, 就让 *Man 自动拥有了 get 方法
类型断言还有一种形式, 即 switch 判断接口类型, case 会顺序的执行, 当命中一个 case 时,就会执行 case 中的语句,因此 case 语句的顺序是很重要的,因为很有可能会有多个 case 匹配的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
funcclassifier(items ...interface{}) { for i, x := range items { switch x.(type) { casebool: fmt.Printf("Param #%d is a bool\n", i) casefloat64: fmt.Printf("Param #%d is a float64\n", i) caseint, int64: fmt.Printf("Param #%d is a int\n", i) casenil: fmt.Printf("Param #%d is a nil\n", i) casestring: fmt.Printf("Param #%d is a string\n", i) default: fmt.Printf("Param #%d is unknown\n", i) } } }
// 根据 inter, typ 计算出 hash 值 h := itabhash(inter, typ)
// look twice - once without lock, once with. // common case will be no lock contention. 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 }
// 检查 _type 是否符合 interface_type 并且创建对应的 itab 结构体 将其放到 hash 表中 funcadditab(m *itab, locked, canfail bool) { inter := m.inter typ := m._type x := typ.uncommon()
// both inter and typ have method sorted by name, // and interface names are unique, // so can iterate over both in lock step; // the loop is O(ni+nt) not O(ni*nt). // // inter 和 typ 的方法都按方法名称进行了排序 // 并且方法名都是唯一的。所以循环的次数是固定的 // 只用循环 O(ni+nt),而非 O(ni*nt) 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 } } } // ……