zoukankan      html  css  js  c++  java
  • go——反射

    反射(reflect)让我们能在运行期探知对象地类型信息和内存结构,这从一定程度上弥补了静态语言在动态行为上地不足。
    和C数据结构一样,Go对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息地。
    反射操作所需地全部信息都源自接口变量。接口变量除存储自身类型外,还会保存实际对象地类型数据。

    func TypeOf(i interface{}) Type
    func ValueOf(i interface{}) Value

    这两个反射入口函数,会将任何传入的对象转换为接口类型
    在面对类型时,需要区分Type和Kind。前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type X int
    
    func main() {
    	var a X = 100
    	t := reflect.TypeOf(a)
    
    	fmt.Println(t.Name(), t.Kind()) //X  int
    }
    

    所以在类型判断上,须选择正确方式。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type X int
    type Y int
    
    func main() {
    	var a, b X = 100, 200
    	var c Y = 300
    
    	ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
    	fmt.Println(ta) //main.X
    	fmt.Println(tb) //main.X
    	fmt.Println(tc) //main.Y
    
    	fmt.Println(ta == tb, ta == tc)     //true false
    	fmt.Println(ta.Kind() == tc.Kind()) //true
    }
    

    除通过实践对象获取类型外,也可以直接构造一些基础复合类型。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
    	m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
    
    	fmt.Println(a, m) //[10]uint8 map[string]int
    }
    

    传入对象应区分基类型和指针类型,因为它们并不属于同一类型。
    方法Elem返回指针、数组、切片、字典(值)或通道地基类型。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	x := 100
    
    	tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
    
    	fmt.Println(tx, tp, tx == tp)     //int *int false
    	fmt.Println(tx.Kind(), tp.Kind()) //int ptr
    	fmt.Println(tx == tp.Elem())      //true
    
    	fmt.Println(reflect.TypeOf(map[string]int{}).Elem()) //int
    	fmt.Println(reflect.TypeOf([]int32{}).Elem())        //int32
    }
    

    只有在获取结构体指针地基类型之后,才能遍历它的字段。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type user struct {
    	name string
    	age  int
    }
    
    type manager struct {
    	user  //只有类型而没有名字,所以属于匿名字段
    	title string
    }
    
    func main() {
    	var m manager
    	t := reflect.TypeOf(&m) //类型属性信息
    	fmt.Println(t)          //*main.manager
    
    	if t.Kind() == reflect.Ptr { //是否为指针类型
    		t = t.Elem()
    	}
    	fmt.Println(t.NumField())           //2
    	for i := 0; i < t.NumField(); i++ { //类型属性中包含地的字段
    		f := t.Field(i) //取具体的字段
    		fmt.Println(f.Name, f.Type, f.Offset)
    
    		if f.Anonymous { //输出匿名字段结构
    			for x := 0; x < f.Type.NumField(); x++ {
    				af := f.Type.Field(x)
    				fmt.Println("  ", af.Name, af.Type)
    			}
    		}
    	}
    }
    
    /*
    user main.user 0
       name string
       age int
    title string 24
    */
    

    对于匿名字段,可用于多级索引(按定义顺序)直接访问。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type user struct {
    	name string
    	age  int
    }
    
    type manager struct {
    	user
    	title string
    }
    
    func main() {
    	var m manager
    
    	t := reflect.TypeOf(m)
    	fmt.Println(t)                    //main.manager
    	name, _ := t.FieldByName("name")  //按照字段名称查找,
    	fmt.Println(name)                 //{name main string  0 [0 0] false}  取到的是一个对象实体
    	fmt.Println(name.Name, name.Type) //name string
    
    	age := t.FieldByIndex([]int{0, 1}) //按多级索引查找  //0——》user  1——》age
    	fmt.Println(age.Name, age.Type)    //age int
    }
    

    FieldByName不支持多级名称,如有遮蔽,需通过匿名字段二次获取。

    同样,输出方法集时,一样区分基类型和指针类型。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type A int
    
    type B struct {
    	A
    }
    
    func (A) av()  {}
    func (*A) ap() {}
    func (B) bv()  {}
    func (*B) bp() {}
    
    func main() {
    	var b B
    	t := reflect.TypeOf(&b)
    	s := []reflect.Type{t, t.Elem()}
    
    	for _, t := range s {
    		fmt.Println(t, ":")
    
    		for i := 0; i < t.NumMethod(); i++ {
    			fmt.Println("  ", t.Method(i))
    		}
    	}
    }
    

    有一点和想象不同,反射能探知当前包或外包的非导出结构成员
    相对reflect而言,当前包和外包都是“外包”。

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"reflect"
    )
    
    func main() {
    	var s http.Server
    	t := reflect.TypeOf(s)
    
    	for i := 0; i < t.NumField(); i++ {
    		fmt.Println(t.Field(i).Name)
    	}
    }
    
    /*
    Addr
    Handler
    TLSConfig
    ReadTimeout
    ReadHeaderTimeout
    WriteTimeout
    IdleTimeout
    MaxHeaderBytes
    TLSNextProto
    ConnState
    ErrorLog
    disableKeepAlives
    inShutdown
    nextProtoOnce
    nextProtoErr
    mu
    listeners
    activeConn
    doneChan
    onShutdown
    */
    

    可用反射提取struct tag,还能自动分解。其常用于ORM映射,或数据格式验证。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type user struct {
    	name string `field:"name" type:"varchar(50)"`
    	age  int    `field:"age"  type:"int"`
    }
    
    func main() {
    	var u user
    	t := reflect.TypeOf(u)
    	for i := 0; i < t.NumField(); i++ {
    		f := t.Field(i)
    		fmt.Printf("%s:%s %s
    ", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
    	}
    }
    
    /*
    name:name varchar(50)
    age:age int
    */
    

    和Type获取类型信息不同,value专注于对象实例数据读写。
    之前说过,接口变量会赋值对象,且是unaddressable的,所以要想修改目标对象,就必须使用指针。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	a := 100
    	va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()
    
    	fmt.Println(va.CanAddr(), va.CanSet()) //false false
    	fmt.Println(vp.CanAddr(), vp.CanSet()) //true true
    }

    就算传入指针,一样需要通过Elem获取目标对象。
    因为被接口存储的指针本身是不能寻址和进行设置操作的。

    注意,不能对非导出字段直接进行设置操作,无论是当前包还是外包。

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"unsafe"
    )
    
    type User struct {
    	Name string
    	code int
    }
    
    func main() {
    	p := new(User)
    	v := reflect.ValueOf(p).Elem()
    
    	name := v.FieldByName("Name")
    	code := v.FieldByName("code")
    
    	fmt.Printf("name: canaddr = %v, canset = %v
    ", name.CanAddr(), name.CanSet())
    	fmt.Printf("code: canaddr = %v, canset = %v
    ", code.CanAddr(), code.CanSet())
    
    	if name.CanSet() {
    		name.SetString("kebi")
    	}
    
    	if code.CanAddr() {
    		*(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
    	}
    	fmt.Printf("%+v
    ", *p)
    }
    
    /*
    name: canaddr = true, canset = true
    code: canaddr = true, canset = false
    {Name:kebi code:100}
    */
    

    可通过Interface方法进行类型推断和转换。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	type user struct {
    		Name string
    		Age  int
    	}
    
    	u := user{
    		"kebi",
    		26,
    	}
    
    	v := reflect.ValueOf(&u)
    
    	if !v.CanInterface() {
    		fmt.Println("caninterface:fail")
    		return
    	}
    
    	p, ok := v.Interface().(*user)
    	if !ok {
    		fmt.Println("interface:fail")
    		return
    	}
    	p.Age++
    	fmt.Printf("%+v
    ", u) //{Name:kebi Age:27}
    }
    

    也可以直接使用value.Int,Bool等方法进行类型转换,但失败时会引发panic,且不支持ok-idiom。
    复合类型对象设置示例。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	c := make(chan int, 4)
    	v := reflect.ValueOf(c)
    
    	if v.TrySend(reflect.ValueOf(100)) {
    		fmt.Println(v.TryRecv()) //100 true
    	}
    }
    

    接口有两种nil状态,这是一个潜在麻烦。解决方法是用IsNil判断值是否为nil。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	var a interface{} = nil
    	var b interface{} = (*int)(nil)
    
    	fmt.Println(a == nil)                             //true
    	fmt.Println(b == nil, reflect.ValueOf(b).IsNil()) //false  true
    }
    

    也可用unsafe转换后直接判断iface.data是否为零值。

    package main
    
    import (
    	"fmt"
    	"unsafe"
    )
    
    func main() {
    	var b interface{} = (*int)(nil)
    	ifac := (*[2]uintptr)(unsafe.Pointer(&b))
    
    	fmt.Println(ifac, ifac[1] == 0) //&[4825024 0] true
    }
    

    动态调用方法,只须按照in列表准备好所需参数即可。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type X struct{} //结构体
    
    func (X) Test(x, y int) (int, error) { //方法
    	return x + y, fmt.Errorf("err: %d", x+y)
    }
    
    func main() {
    	var a X
    	v := reflect.ValueOf(&a)    //
    	m := v.MethodByName("Test") //根据方法名获取程序实体
    
    	in := []reflect.Value{ //构建切片数据
    		reflect.ValueOf(1),
    		reflect.ValueOf(2),
    	}
    
    	out := m.Call(in)
    	for _, v := range out {
    		fmt.Println(v)
    	}
    }
    
    /*
    3
    err: 3
    */
    

    对于变参来说,用CallSlice要更方便一些。

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type X struct{}
    
    func (X) Format(s string, a ...interface{}) string {
    	return fmt.Sprintf(s, a...)
    }
    
    func main() {
    	var a X
    
    	v := reflect.ValueOf(&a)
    	m := v.MethodByName("Format")
    
    	out := m.Call([]reflect.Value{
    		reflect.ValueOf("%s = %d"),
    		reflect.ValueOf("x"),
    		reflect.ValueOf(100),
    	})
    
    	fmt.Println(out)
    
    	out = m.CallSlice([]reflect.Value{
    		reflect.ValueOf("%s = %d"),
    		reflect.ValueOf([]interface{}{"x", 100}),
    	})
    	fmt.Println(out)
    }
    
    /*
    [x = 100]
    [x = 100]
    */
    

      

  • 相关阅读:
    卡特兰数
    hdu 1023 Train Problem II
    hdu 1022 Train Problem
    hdu 1021 Fibonacci Again 找规律
    java大数模板
    gcd
    object dection资源
    Rich feature hierarchies for accurate object detection and semantic segmentation(RCNN)
    softmax sigmoid
    凸优化
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/10090034.html
Copyright © 2011-2022 走看看