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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM