go进阶-接口
只要某个类型实现了一个接口定义的方法集,那就可以认为这个类型实现了接口,是接口类型。
定义
type Human interface {
Say(s string) error
// ...方法集
}
demo
type Animal interface {
Say()
}
type Pig struct {
name string
}
type Horse struct {
name string
}
func (di *Pig) Say() {
di.name = "yadi pig" // 指针能改变参数
fmt.Println("Hi", di.name)
}
func (horse Horse) Say() {
horse.name = "yadi horse" // 不能改变外部horse name
fmt.Println("Hi", horse.name)
}
func SomeAnimalSay(animal Animal) {
animal.Say()
}
func main() {
p := Pig{"little pig"}
h := Horse{"Big horse"}
SomeAnimalSay(&p) // Hi yadi pig
SomeAnimalSay(&h) // Hi yadi horse
fmt.Printf("p.name: %v\n", p.name)
fmt.Printf("h.name: %v\n", h.name)
// p.name: yadi pig
// h.name: Big horse
}
数据结构
Go 接口镀层在运行时分为两类:
runtime.iface
runtime.eface
eface
用于表示没有方法的空接口(empty interface)类型变量,也就是 interface{}类型的变量;
iface
用于表示其余拥有方法的接口 interface 类型变量。
eface
type eface struct {
_type *_type // 底层的类型信息
data unsafe.Pointer // 指向的值信息指针
}
这是个空接口,不含任何方法。
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
}
align
和fieldAlign
的区别?
内存对齐
首先,对齐是用空间换时间,完成块读取、写入。如图:
预计是32字节。
type A struct { a bool b int32 c int8 d int64 e byte } func main() { p1 := A{} fmt.Printf("unsafe.Sizeof(p1): %v\n", unsafe.Sizeof(p1)) //unsafe.Sizeof(p1): 32 }
符合预料。
如果要节省内存,写法应为:
type A struct { a bool c int8 e byte b int32 d int64 } func main() { p1 := A{} fmt.Printf("unsafe.Sizeof(p1): %v\n", unsafe.Sizeof(p1)) //unsafe.Sizeof(p1): 16 }
align:一个变量在内存中对齐后所用的字节数。
FieldAlign:这种类型的变量如果是struct中的字段,对齐值。
type A struct {
a bool
b int32
c int8
d int64
e byte
}
func main() {
p1 := A{false, 6, 1, 12, 1}
var bo bool
var i int16
fmt.Printf("unsafe.Alignof(bo): %v\n", unsafe.Alignof(bo)) // 1
fmt.Printf("unsafe.Alignof(i): %v\n", unsafe.Alignof(i)) // 2
fmt.Printf("reflect.TypeOf(p1).Align(): %v\n", reflect.TypeOf(p1).Align()) // 8
fmt.Printf("reflect.TypeOf(p1).FieldAlign(): %v\n", reflect.TypeOf(p1).FieldAlign()) // 8
fmt.Printf("reflect.TypeOf(p1).Size(): %v\n", reflect.TypeOf(p1).Size()) // 32
}
iface
type iface struct {
tab *itab
data unsafe.Pointer
}
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 interfacetype struct {
typ _type // 接口具体类型信息
pkgpath name // 接口包信息
mhdr []imethod // 接口定义的函数列表
}
type imethod struct {
name nameOff
ityp typeOff
}
// name and offset
type nameOff struct {
n *ir.Name
off int64
}
// Value
Name struct {
Value string
expr
}
type expr struct{ node }
type Pos struct {
base *PosBase
line, col uint32
}
// A PosBase represents the base for relative position information:
// At position pos, the relative position is filename:line:col.
type PosBase struct {
pos Pos
filename string
line, col uint32
trimmed bool // whether -trimpath has been applied
}
真尼玛老母猪dxz。
类型断言
func main() {
var i interface{} = "string"
s := i.(string)
fmt.Printf("1 s: %v\n", s)
s, ok := i.(string)
fmt.Printf("2 ok: %v\n", ok)
switch i.(type) {
case string:
fmt.Println("string..")
case int:
fmt.Println("int")
default:
fmt.Println("else")
}
}
// 1 s: string
// 2 ok: true
// string..
类型转换

动态分派
之前在iface里的func提到了这个:
type iface struct {
tab *itab
data unsafe.Pointer
}
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.
// 存储接口方法集的具体实现,包含一组函数指针,实现了接口方法的动态分配,在每次接口发生变更时都会更新。
}
func
属性类型是[1]uintptr
,只有一个元素,存放了接口方法集的首个方法的地址信息,接着顺序获取就好了。
itab
包含接口的静态类型信息、数据的动态类型信息、函数表。
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
// ......
var m *itab
// First, look in the existing table to see if we can find the itab we need.
// This is by far the most common case, so do it without locks.
// Use atomic to ensure we see any previous writes done by the thread
// that updates the itabTable field (with atomic.Storep in itabAdd).
t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
if m = t.find(inter, typ); m != nil {
goto finish
}
// Not found. Grab the lock and try again.
lock(&itabLock)
if m = itabTable.find(inter, typ); m != nil {
unlock(&itabLock)
goto finish
}
// Entry doesn't exist yet. Make a new entry & add it.
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
// The hash is used in type switches. However, compiler statically generates itab's
// for all interface/type pairs used in switches (which are added to itabTable
// in itabsinit). The dynamically-generated itab's never participate in type switches,
// and thus the hash is irrelevant.
// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
m.hash = 0
m.init()
itabAdd(m)
unlock(&itabLock)
finish:
if m.fun[0] != 0 {
return m
}
if canfail {
return nil
}
// this can only happen if the conversion
// was already done once using the , ok form
// and we have a cached negative result.
// The cached result doesn't record which
// interface function was missing, so initialize
// the itab again to get the missing function name.
panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}
第一个不加锁,查缓存,如果没找到,加锁查找,可能另一个协程并发写入,导致未找到,但是数据是存在的;如果还是找不到,插入。