zoukankan      html  css  js  c++  java
  • Go通关08:断言、反射的理解与使用

    接口断言

    提到接口断言,我们先回顾下怎么实现接口?

    • 接口的实现者必须是一个具体类型
    • 类型定义的方法和接口里方法名、参数、返回值都必须一致
    • 若接口有多个方法,那么要实现接口中的所有方法

    对于空接口 interface{} ,因为它没有定义任何的函数(方法),所以说Go中的所有类型都实现了空接口。

    当一个函数的形参是 interface{} 时,意味着这个参数被自动的转为interface{} 类型,在函数中,如果想得到参数的真实类型,就需要对形参进行断言。

    • 类型断言就是将接口类型的值x,转换成类型T,格式为:x.(T)
    • 类型断言x必须为接口类型
    • T可以是非接口类型,若想断言合法,则T必须实现x的接口

    语法格式:

    //非安全类型断言
    <目标类型的值> := <表达式>.( 目标类型 )
    // 安全类型断言
    <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
    

    示例

    package main
    import "fmt"
    
    func whoAmi(a interface{}) {
        //1.不断言
        //程序报错:cannot convert a (type interface{}) to type string: need type assertion
        //fmt.Println(string(a)) 
      
        //2.非安全类型断言
        //fmt.Println(a.(string)) //无尘
      
        //3.安全类型断言
        value, ok := a.(string) //安全,断言失败,也不会panic,只是ok的值为false
        if !ok {
          fmt.Println("断言失败")
          return
        }
        fmt.Println(value)  //无尘
    }
    func main() {
        str := "无尘"
        whoAmi(str)
    }
    

    断言还有一种形式,就是使用switch语句判断接口的类型:

    func whoAmi(a interface{}) {
        switch a.(type) {
        case bool:
    		fmt.Printf("boolean: %t
    ", a) // a has type bool
        case int:
    		fmt.Printf("integer: %d
    ", a) // a has type int
        case string:
    		fmt.Printf("string: %s
    ", a) // a has type string
        default:
        fmt.Printf("unexpected type %T", a) // %T prints whatever type a has
        }
    }
    

    反射

    Go语言提供了一种机制,在运行时可以更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。

    反射有何用

    • 上面我们提到空接口,它能接收任何东西
    • 但是怎么来判断空接口变量存储的是什么类型呢?上面介绍的类型断言可以实现
    • 如果想获取存储变量的类型信息和值信息就需要使用到反射
    • 反射就是可以动态获取变量类型信息和值信息的机制

    reflect 包

    反射是由reflect包来提供支持的,它提供两种类型来访问接口变量的内容,即Type 和 Value。
    reflect包提供了两个函数来获取任意对象的Type 和 Value:

    1. func TypeOf(i interface{}) Type
    2. func ValueOf(i interface{}) Value
    函数 作用
    reflect.TypeOf() 获取变量的类型信息,如果为空则返回nil
    reflect.ValueOf() 获取数据的值,如果为空则返回0

    示例:

    package main
    import (
      "fmt"
      "reflect"
    )
    func main() {
      var name string = "微客鸟窝"
      // TypeOf会返回变量的类型,比如int/float/struct/指针等
    	reflectType := reflect.TypeOf(name)
    
    	// valueOf返回变量的的值,此处为"微客鸟窝"
    	reflectValue := reflect.ValueOf(name)
    
    	fmt.Println("type: ", reflectType) //type:  string
    	fmt.Println("value: ", reflectValue) //value:  微客鸟窝
    }
    
    1. 函数 TypeOf 的返回值 reflect.Type 实际上是一个接口,定义了很多方法来获取类型相关的信息:
    type Type interface {
        // 所有的类型都可以调用下面这些函数
    
        // 此类型的变量对齐后所占用的字节数
        Align() int
        
        // 如果是 struct 的字段,对齐后占用的字节数
        FieldAlign() int
    
        // 返回类型方法集里的第 `i` (传入的参数)个方法
        Method(int) Method
    
        // 通过名称获取方法
        MethodByName(string) (Method, bool)
    
        // 获取类型方法集里导出的方法个数
        NumMethod() int
    
        // 类型名称
        Name() string
    
        // 返回类型所在的路径,如:encoding/base64
        PkgPath() string
    
        // 返回类型的大小,和 unsafe.Sizeof 功能类似
        Size() uintptr
    
        // 返回类型的字符串表示形式
        String() string
    
        // 返回类型的类型值
        Kind() Kind
    
        // 类型是否实现了接口 u
        Implements(u Type) bool
    
        // 是否可以赋值给 u
        AssignableTo(u Type) bool
    
        // 是否可以类型转换成 u
        ConvertibleTo(u Type) bool
    
        // 类型是否可以比较
        Comparable() bool
    
        // 下面这些函数只有特定类型可以调用
        // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
        
        // 类型所占据的位数
        Bits() int
    
        // 返回通道的方向,只能是 chan 类型调用
        ChanDir() ChanDir
    
        // 返回类型是否是可变参数,只能是 func 类型调用
        // 比如 t 是类型 func(x int, y ... float64)
        // 那么 t.IsVariadic() == true
        IsVariadic() bool
    
        // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
        Elem() Type
    
        // 返回结构体类型的第 i 个字段,只能是结构体类型调用
        // 如果 i 超过了总字段数,就会 panic
        Field(i int) StructField
    
        // 返回嵌套的结构体的字段
        FieldByIndex(index []int) StructField
    
        // 通过字段名称获取字段
        FieldByName(name string) (StructField, bool)
    
        // FieldByNameFunc returns the struct field with a name
        // 返回名称符合 func 函数的字段
        FieldByNameFunc(match func(string) bool) (StructField, bool)
    
        // 获取函数类型的第 i 个参数的类型
        In(i int) Type
    
        // 返回 map 的 key 类型,只能由类型 map 调用
        Key() Type
    
        // 返回 Array 的长度,只能由类型 Array 调用
        Len() int
    
        // 返回类型字段的数量,只能由类型 Struct 调用
        NumField() int
    
        // 返回函数类型的输入参数个数
        NumIn() int
    
        // 返回函数类型的返回值个数
        NumOut() int
    
        // 返回函数类型的第 i 个值的类型
        Out(i int) Type
    
        // 返回类型结构体的相同部分
        common() *rtype
        
        // 返回类型结构体的不同部分
        uncommon() *uncommonType
    }
    
    1. 函数 TypeOf 的返回值 reflect.Value 是一个结构体类型。Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:
    // 设置切片的 len 字段,如果类型不是切片,就会panic
     func (v Value) SetLen(n int)
     
     // 设置切片的 cap 字段
     func (v Value) SetCap(n int)
     
     // 设置字典的 kv
     func (v Value) SetMapIndex(key, val Value)
    
     // 返回切片、字符串、数组的索引 i 处的值
     func (v Value) Index(i int) Value
     
     // 根据名称获取结构体的内部字段值
     func (v Value) FieldByName(name string) Value
     
     // ……
    

    struct反射示例:

    package main
    
    import (
       "fmt"
       "reflect"
    )
    
    type Address struct {
     City string
    }
    
    type Person struct {
     Name string
     Age uint
     Address // 匿名字段
    }
    
    func (p Person) Hello(){
       fmt.Println("我是无尘啊")
    }
    
    func main() {
       //p := Person{Name:"无尘",Age:18,Address:Address{City:"北京"}}  //map赋值
       p := Person{"无尘",18,Address{"北京"}}
    
       // 获取目标对象
       t := reflect.TypeOf(p)
       fmt.Println("t:", t)
     
       // .Name()可以获取去这个类型的名称
       fmt.Println("类型的名称:", t.Name())
    
       // 获取目标对象的值类型
       v := reflect.ValueOf(p)
       fmt.Println("v:", v)
       
       // .NumField()获取其包含的字段的总数
       for i := 0; i < t.NumField(); i++ {
         // 从0开始获取Person所包含的key
         key := t.Field(i)
         // interface方法来获取key所对应的值
         value := v.Field(i).Interface()
         fmt.Printf("第%d个字段是:%s:%v = %v 
    ", i+1, key.Name, key.Type, value)
       }
       // 取出这个City的详情打印出来
       fmt.Printf("%#v
    ", t.FieldByIndex([]int{2, 0}))
       // .NumMethod()来获取Person里的方法
       for i:=0;i<t.NumMethod(); i++ {
         m := t.Method(i)
         fmt.Printf("第%d个方法是:%s:%v
    ", i+1, m.Name, m.Type)
       }
    }
    

    运行结果:

    t: main.Person
    类型的名称: Person
    v: {无尘 18 {北京}}
    第1个字段是:Name:string = 无尘 
    第2个字段是:Age:uint = 18 
    第3个字段是:Address:main.Address = {北京} 
    reflect.StructField{Name:"City", PkgPath:"", Type:(*reflect.rtype)(0x4cfe60), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
    第1个方法是:Hello:func(main.Person)
    
    1. 通过反射修改内容
    package main
    
    import (
    	"reflect"
    	"fmt"
    )
    
    type Person struct {
    	Name string
    	Age int
    }
    
    func main() {
    	p := &Person{"无尘",18}
    	v := reflect.ValueOf(p)
    
    	// 修改值必须是指针类型
    	if v.Kind() != reflect.Ptr {
    		fmt.Println("非指针类型,不能进行修改")
    		return
    	}
    
    	// 获取指针所指向的元素
    	v = v.Elem()
    	// 获取目标key的Value的封装
    	name := v.FieldByName("Name")
    
    	if name.Kind() == reflect.String {
    		name.SetString("wucs")
    	}
    
    	fmt.Printf("%#v 
    ", *p)
    
    
    	// 如果是整型的话
    	test := 666
    	testV := reflect.ValueOf(&test)
    	testV.Elem().SetInt(999)
    	fmt.Println(test)
    }
    

    运行结果:

    main.Person{Name:"wucs", Age:18} 
    999
    
    1. 通过反射调用方法
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	Name string
    	Age int
    }
    
    func (p Person) EchoName(name string){
    	fmt.Println("我的名字是:", name)
    }
    
    func main() {
    	p := Person{Name: "无尘",Age: 18}
    
    	v := reflect.ValueOf(p)
    
    	// 获取方法控制权
    	// 官方解释:返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装
    	mv := v.MethodByName("EchoName")
    	// 拼凑参数
    	args := []reflect.Value{reflect.ValueOf("wucs")}
    
    	// 调用函数
    	mv.Call(args)
    }
    

    运行结果:

    我的名字是: wucs
    
  • 相关阅读:
    Atitit java支持php运行环境 Quercus jar 1.1. Quercus 1 1.2. Web.xml 增加php servlet拦截 1 1.3. Phpinfo。php测试 1
    EXTJS学习系列提高篇:第十一篇(转载)作者殷良胜,制作树形菜单之五
    EXTJS学习系列基础篇:第八篇(转载)作者殷良胜,Ext组件系列之textfield组件的基本用法
    基础篇:第五篇,Ext.util.Format类是Ext对数据进行格式化操作的一个类(转载)作者殷良胜
    EXTJS学习系列基础篇:第七篇(转载)作者殷良胜,Ext组件系列之label组件的基本用法
    EXTJS学习系列提高篇:第二篇(转载)作者殷良胜,结合EXT2.2+C#.net实现将数据导入Excel的功能
    EXTJS学习系列基础篇:第九篇(转载)作者殷良胜,Ext组件系列之field组件的基本用法
    EXTJS学习系列基础篇:第二篇(转载)作者殷良胜
    Ext 智能 在VS2008中让Intellisense提供对ExtJS的支持 (转载)作者殷良胜
    EXTJS学习系列提高篇:第十篇(转载)作者殷良胜,制作树形菜单之四
  • 原文地址:https://www.cnblogs.com/isungge/p/15122209.html
Copyright © 2011-2022 走看看