前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go通关08:断言、反射的理解与使用!

Go通关08:断言、反射的理解与使用!

作者头像
微客鸟窝
发布2021-08-18 15:20:10
1K0
发布2021-08-18 15:20:10
举报
文章被收录于专栏:Go语言指北

您诸位好啊,我是无尘,学习Go语言肯定经常看到断言、反射这两个词,曾因为使用场景不太熟悉,让我很是费解,今天就好好唠唠!

接口断言

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

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

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

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

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

语法格式:

代码语言:javascript
复制
//非安全类型断言
<目标类型的值> := <表达式>.( 目标类型 )
// 安全类型断言
<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
示例
代码语言:javascript
复制
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语句」判断接口的类型:

代码语言:javascript
复制
func whoAmi(a interface{}) {
    switch a.(type) {
    case bool:
  fmt.Printf("boolean: %t\n", a) // a has type bool
    case int:
  fmt.Printf("integer: %d\n", a) // a has type int
    case string:
  fmt.Printf("string: %s\n", 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」

示例:

代码语言:javascript
复制
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 实际上是一个接口,定义了很多方法来获取类型相关的信息:
代码语言:javascript
复制
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 所指向的实际数据:
代码语言:javascript
复制
// 设置切片的 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反射示例:

代码语言:javascript
复制
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 \n", i+1, key.Name, key.Type, value)
   }
   // 取出这个City的详情打印出来
   fmt.Printf("%#v\n", t.FieldByIndex([]int{2, 0}))
   // .NumMethod()来获取Person里的方法
   for i:=0;i<t.NumMethod(); i++ {
     m := t.Method(i)
     fmt.Printf("第%d个方法是:%s:%v\n", i+1, m.Name, m.Type)
   }
}

运行结果:

代码语言:javascript
复制
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. 通过反射修改内容
代码语言:javascript
复制
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 \n", *p)


 // 如果是整型的话
 test := 666
 testV := reflect.ValueOf(&test)
 testV.Elem().SetInt(999)
 fmt.Println(test)
}

运行结果:

代码语言:javascript
复制
main.Person{Name:"wucs", Age:18} 
999
  1. 通过反射调用方法
代码语言:javascript
复制
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)
}

运行结果:

代码语言:javascript
复制
我的名字是:wucs

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微客鸟窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 接口断言
    • 语法格式:
    • 反射
      • 反射有何用
        • reflect 包
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档