082-反射(結構體欄位遍歷)
阿新 • • 發佈:2018-12-10
如何知道一個未知結構體包含哪些欄位呢?利用反射,可以很容易做到。
1. 遍歷結構體的 field 和 method
還記得 reflect.Type 介面吧,這個介面還包含這 4 個方法:
type interface Type {
...
NumField() int
Field(i int) StructField
NumMethod() int
Method(int) Method
...
}
也就是說,只要你能拿到 Type 型別的介面值,就可以知道結構體包含了幾個欄位,幾個方法。通過 NumField 和 Method 方法,你就可以獲取關於第 i 個 field 和 method 的具體資訊。
!!!注意:只有 Kind 為 Struct 的 Type 才可以呼叫上面這 4 個方法,否則程式會 panic.
field 和 method 的資訊是通過 StructField 和 Method 型別進行描述的:
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
2. 示例
package main
import (
"fmt"
"reflect"
)
type Data struct {
weight uint32
height uint32
}
// 定義一個結構體
type Person struct {
Name string `tips:this is name`
age int32 `how old are you?`
Data
}
// 為 *Person 定義方法
func (p *Person) GetName() string {
return p.Name
}
func (p *Person) GetAge() int32 {
return p.age
}
func main() {
p := Person{
"allen",
19,
Data{
50,
180,
},
}
// 1. 取到 type 介面值
t := reflect.TypeOf(p)
fmt.Println("欄位列舉:")
fmt.Println("------------------------------")
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("index:%d\nName:%s\nPkgPath:%s\nType:%v\nTag:%s\nOffset:%v\nIndex:%v\nAnonymous:%v\n",
i, f.Name, f.PkgPath, f.Type, f.Tag, f.Offset, f.Index, f.Anonymous)
fmt.Println("------------------------------")
}
fmt.Println("方法列舉:")
fmt.Println("------------------------------")
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("index:%d\nName:%s\nPkgPath:%s\nType:%v\nFunc:%v\nIndex:%v\n",
i, m.Name, m.PkgPath, m.Type, m.Func, m.Index)
fmt.Println("------------------------------")
}
}
輸出結果:
欄位列舉:
------------------------------
index:0
Name:Name
PkgPath:
Type:string
Tag:tips:this is name
Offset:0
Index:[0]
Anonymous:false
------------------------------
index:1
Name:age
PkgPath:main
Type:int32
Tag:how old are you?
Offset:16
Index:[1]
Anonymous:false
------------------------------
index:2
Name:Data
PkgPath:
Type:main.Data
Tag:
Offset:20
Index:[2]
Anonymous:true
------------------------------
方法列舉:
------------------------------
有同學會很好奇,為什麼方法一個都沒有遍歷出來?不知道你是否還記得“方法的接收器”相關的知識。在上面的例子裡,方法的接收器是指標型別,這意味著那兩個方法並沒有定義在 Person 型別上,而是定義在 *Person 上。
你使用 TypeOf(p) 拿到的型別是關於 Person 的型別資訊,而不是 *Person 型別資訊。因此,如果你想列舉方法,就只能使用 TypeOf(&p) 拿到關於 *Person 的型別資訊,才可以正確的枚舉出方法。
3. 如何列印欄位對應的值
欄位名也有了,那麼欄位的值怎麼獲取呢?
要知道,Type 介面只能拿到關於型別的資訊,無法拿到 Value 的資訊。如果想獲取欄位值,就只能從 Value 這個結構體入手了。同樣的,Value 也提供了 4 個方法:
type struct Value {
...
}
func (v Value) NumField() int
func (v Value) Field(i int) Value
func (v Value) NumMethod() int
func (v Value) Method(i int) Value
不過,這 4 個方法和 Type 介面那 4 個方法返回值不一樣,它返回的又是 Value 型別。好了,剩下的就是碼程式碼了,這個實在是太簡單,同學們自己完全可以寫出來。
4. 總結
- 知道如何遍歷結構體的欄位和方法
- 知道 Type 介面關於遍歷欄位和方法的方法與 Value 的區別