interface

空接口与非空接口结构体

struct Eface	// interface 无方法时底层数据结果
{
    Type*    type;
    void*    data;
};

struct Type
{
    uintptr size;		// 类型的大小
    uint32 hash;
    uint8 _unused;
    uint8 align;		// 对齐
    uint8 fieldAlign;	// 嵌入结构体时的对齐
    uint8 kind;			// 枚举值
    Alg *alg;			// 函数指针的数组, 存储了 hash/equal/print/copy 四个函数操作
    void *gc;
    String *string;
    UncommonType *x;	// 所指向的结构体中包含一张排序过的方法表, 包含该接口要求实现的方法
    Type *ptrto;
};

struct Iface	// interface 有方法时底层数据结构
{
    Itab*    tab;
    void*    data;
};

struct Itab
{
    InterfaceType* inter;	// 所指向的结构体中包含一张排序过的方法表, 包含该接口要求实现的方法
    Type*    type;
    Itab*    link;
    int32    bad;
    int32    unused;
    void    (*fun[])(void);	// Iface 中的具体类型中实现的方法会被拷贝到 Itab 的 fun 数组中
};
  • 将某个类型转换为成空接口

    将 Eface 中 type 指向原始数据类型, data 指向原型中的数据

  • 将某个类型转换为带方法的接口

    必须实现接口中的所有方法才可以进行转换

    type I interface {
        String()
    }
    var a int = 5
    var b I = a
    
    /* 编译时报错
    cannot use a (type int) as type I in assignment:
            int does not implement I (missing String method)
    */
    

    Type 的 UncommonType 有一个方法表, 包含某个类型所实现的所有方法, reflect包中的 Method 和 MethodByName 方法都是通过查询这张表实现的, 表中的每一项是一个Method, 数据结构:

    struct Method
    {
        String *name;
        String *pkgPath;
        Type    *mtyp;
        Type *typ;
        void (*ifn)(void);
        void (*tfn)(void);
    };
    

    接口中的 InterfaceType 有一个方法表, 包含这个接口所要求实现的所有方法, 其中每一项是一个IMethod,数据结构如下:

    struct IMethod
    {
        String *name;
        String *pkgPath;
        Type *type;
    };
    

    与上面的 Method 相比可以看出这里只有声明没有实现

    Iface 中的 Itab 的 func 是一个方法表, 表中每一项都是一个函数指针, 也是只有声明没有实现

    类型转换时校验 Type 中的方法表是否包含 InterfaceType 的方法表中的所有方法, 并把 Type 方法表中的实现部分拷贝到 Itab 的 func 表中

参考:
7.2 interface

interface{}

interface{} 是一个空的 interface 类型. 若一个类型实现了一个 interface 的所有方法, 就说该类型实现了这个 interface, 空的 interface 没有方法, 所以可以认为所有类型都实现了 interface{}, 如果一个函数的参数是 interface{}类型, 那么这个函数就可以接收任何类型作为他的参数

func doSomething(v interface{}){    
}

一个 interface 被多种类型实现时, 可以通过断言或接口方式来判断其类型: value, ok := em.(T)

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // panic
	fmt.Println(f)
}

/*
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64

goroutine 1 [running]:
main.main()
        /home/rhettzhang/learn/golang/interface/main.go:17 +0x1fe
*/

如果需要区分多种类型, 可以使用 switch 断言, 更简单直接, 这种断言方式只能在 switch 语句中使用

switch v := i.(type) {
case T:
    // v 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,v 与 i 的类型相同
}

既然 interface{} 可以接收任意类型, 那么 interface{} 类型的 slice 是否可以接受任意类型的 slice?

func printAll(vals []interface{}) {
	for _, val := range vals {
		fmt.Println(val)
	}
}

func main() {
	names := []string{"stanley", "david", "oscar"}
	printAll(names)
}
/*
./main.go:13:10: cannot use names (type []string) as type []interface {} in argument to printAll
*/

显然不能, golang 没有自动把 slice 转换成 interface{} 类型的 slice, 所以报错, 好像是 interface{}[]interface{} 内存布局不同, 见 InterfaceSlice, 解决方法手动分配一个 []interface{}

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
	interfaceSlice[i] = d
}

参考:
理解 Go interface 的 5 个关键点
InterfaceSlice
类型选择

接口与结构体

结构体可以以值或指针的方式实现结构体接口方法, 官方文档建议:

First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.)

Second is the consideration of efficiency. If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.

Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.

For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.

type Inter interface {
	getArea()
	getDoubleArea()
}

type Rectangle struct {
	width  int
	height int
}

// #1 值接收者
func (r Rectangle) getArea() {
	fmt.Printf("area:%d\n", r.width*r.height)
}

// #2 指针接收者
func (r *Rectangle) getDoubleArea() {
	fmt.Printf("double area:%d\n", r.width*r.height*2)
}
  • 指针形式var stInter Inter = &Rectangle{width: 1, height: 2}

    func main() {
    	var pInter Inter = &Rectangle{width: 1, height: 2}
    	pInter.getArea()
    	pInter.getDoubleArea()
    }
    

    pInterRectangle 的指针类型, 其方法包括集合自动包括 Rectangle 的所有方法, 所以能够编译通过

  • 值方形式 var stInter Inter = Rectangle{width: 1, height: 2}

    func main() {
    	var stInter Inter = Rectangle{width: 1, height: 2}
    	stInter.getArea()
    	stInter.getDoubleArea()
    }
    /*
    ./main.go:24:6: cannot use Rectangle literal (type Rectangle) as type Inter in assignment:
            Rectangle does not implement Inter (getDoubleArea method has pointer receiver)
    */
    

    stInterRectangle 的值类型, 其方法集合不包括其指针类型的方法, 而 Rectangle 以指针形式实现 getDoubleArea, 因此不能编译通过

As the Go specification says, the method set of a type T consists of all methods with receiver type T, while that of the corresponding pointer type *T consists of all methods with receiver *T or T. That means the method set of *T includes that of T, but not the reverse.

一个struct的方法集合中仅会包含它的所有值方法, 而该struct的指针类型的方法集合却囊括了前者的所有方法,包括所有值方法和所有指针方法。

参考:
Should I define methods on values or pointers?
Why do T and *T have different method sets?
【Go 原理】结构体方法:值接收者与指针接收者的区别
方法与指针重定向
选择值或指针作为接收者

底层值为 nil 的接口值

即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。
在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。
注意: 保存了 nil 具体值的接口其自身并不为 nil。

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
	i.M()
	fmt.Println(t.S)	// 不能直接访问

	i = &T{"hello"}
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

/*
(<nil>, *main.T)
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49ac61]

goroutine 1 [running]:
main.main()
        /home/rhettzhang/learn/golang/interface/main.go:28 +0xc1
*/

参考:
底层值为 nil 的接口值

反射

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路径
    Type      Type       // 字段反射类型对象
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否为匿名字段
}
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 声明一个空结构体
	type cat struct {
		Name string
		// 带有结构体tag的字段, 这里不能有空格
		Type int `json:"type" id:"100"`
	}

	// 创建cat的实例
	ins := cat{Name: "mimi", Type: 1}
	valueOfCat := reflect.ValueOf(ins)
	fmt.Printf("reflect.ValueOf:%+v\n", valueOfCat)
	// 获取结构体实例的反射类型对象
	typeOfCat := reflect.TypeOf(ins)
	fmt.Printf("reflect.TypeOf:%+v\n", typeOfCat)
	// 遍历结构体所有成员
	for i := 0; i < typeOfCat.NumField(); i++ {
		// 获取每个成员的结构体字段类型
		fieldType := typeOfCat.Field(i)
		// 输出成员名和tag
		fmt.Printf("name:'%v'\ttag:'%v'\tFiled:%+v\n", fieldType.Name, fieldType.Tag, fieldType)
	}
	// 通过字段名, 找到字段类型信息
	if catType, ok := typeOfCat.FieldByName("Type"); ok {
		// 从tag中取出需要的tag
		fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
	}
}

/*
reflect.ValueOf:{Name:mimi Type:1}
reflect.TypeOf:main.cat
name:'Name'     tag:''  Filed:{Name:Name PkgPath: Type:string Tag: Offset:0 Index:[0] Anonymous:false}
name:'Type'     tag:'json:"type" id:"100"'      Filed:{Name:Type PkgPath: Type:int Tag:json:"type" id:"100" Offset:16 Index:[1] Anonymous:false}
type 100
*/

参考文章
[Go语言通过反射获取结构体的成员类型] (http://c.biancheng.net/view/111.html)

posted @ 2021-07-25 12:44  张飘扬  阅读(470)  评论(0编辑  收藏  举报