美文网首页
Go语言之类型断言与反射

Go语言之类型断言与反射

作者: CaiGuangyin | 来源:发表于2020-04-27 15:54 被阅读0次

目录

小记
    make与new的区别
    map操作
类型断言
反射
    1. 变量的内在机制
    2. 反射与空接口
    3. 怎么分析?
    4. 常用类型的变量类型分析
    5. 通过反射获取结构体的字段值信息和类型信息
    6. 通过反射给结构体字段赋值
    7. 获取结构体方法的类型信息
    8. 通过反射调用结构体的方法
    9. 通过反射获取结构体字段的tag信息

小记

make与new的区别

make()用来分配引用类型的内存,比如map、slice以及channel
new()用来分配除了引用类型以外的所有其他类型的内存,比如int、数组等; new返回类型的指针;

package main

import `fmt`

func main() {
    var a *int = new(int)
    fmt.Println(a) // a是一个int指针类型 0xc00000a0c8
    // a = 100      // 此时不能用a直接赋值,因为a是int指针类型,而100是int类型,两者类型不同,所以不同赋值
    *a = 100 // 必须要在a前面加一个星号*,*a 表示内存地址指向的那块内存空间,*a=100表示将数字100存储到a(内存地址)指向的那块内存空间中;
    fmt.Println(*a)

    var b *[]int = new([]int)
    // (*b)[0] = 1      // 此时b是一个指针类型,它的值是一个内存地址
    fmt.Println(b, &b)       // b的值是一个内存地址,而这个内存地址也需要一块内存空间去存储,所以 &b是取b的内存地址
    fmt.Printf("%p\n", b)    // 打印:0xc0000044c0, 这是b的值
    fmt.Println(*b)          // 是一个空切片,打印:[]
    *b = make([]int, 5, 100) // 给b(内存地址)分配一块内存空间并初始化切片长度为5,容量为100,这里才可以进行索引操作,如下
    (*b)[0] = 10
    (*b)[1] = 20
    (*b)[2] = 30
    fmt.Println(*b) // 打印:[10 20 30 0 0]

    var c = make([]int, 5, 20)
    c[0] = 100
    c[1] = 200
    fmt.Println(c) // 打印:[100 200 0 0 0]
    // 通过上面的例子可以看出,用new()初始化引用类型的变量是多余的
}

map操作

package main

import `fmt`

func main() {
    var m map[string]int  // map声明后未初化是nil
    fmt.Println(m == nil) // true

    // 初始化 方式一:
    m = map[string]int{}
    // 对未初始化的map直接赋值会抛出异常 panic: assignment to entry in nil map
    m["cai"] = 22000
    m["kung"] = 20000
    fmt.Println(m, len(m))

    // 初始化 方式二:
    n := make(map[string]int)
    n["guang"] = 12000
    n["wang"] = 10000
    n["guo"] = 8000
    n["liang"] = 9000
    fmt.Println(n, len(n))

    value, ok := n["wangg"]
    if ok {
        fmt.Println("value:", value)
    } else {
        fmt.Println("value is not exists.")
    }
}

类型断言

空接口与类型断言
空接口类型的变量可以存入任何类型的值

package main

import (
    `fmt`
)

func main() {
    var a = 10
    // testInterface(a)

    var b = "hello world"
    // testInterface(b)

    var c = 3.1415926
    
    // 构造一个万能类型的数组list,什么都有存
    var list []interface{}
    list = append(list, a, b, c)

    findType(list)
}

func testInterface(a interface{})  {
    b, ok := a.(int)
    if ok {
        fmt.Printf("a is int: %d, type: %T\n", b, b) // a is int: 10, type: int
    }

    c, ok := a.(string)
    if ok {
        fmt.Printf("a is string: %s, type: %T\n", c, c)  // a is string: hello world, type: string
    }
}

func findType(a interface{})  {
    list, ok := a.([]interface{})
    if ok {
        for _, value := range list {
            switch v := value.(type) {
            case int:
                fmt.Printf("value: %d, type: %T\n", v, v)
            case string:
                fmt.Printf("value: %s, type: %T\n", v, v)
            case float64:
                fmt.Printf("value: %f, type: %T\n", v, v)
            case []interface{}:
                fmt.Println("type: []interface{}")
            default:
                fmt.Println("unknown type.")
            }
        }
    } else {
        fmt.Println("a is not []interface{} type.")
    }

}
  • b, ok := a.(int) 断言a为int类型,如果断言错误则ok为false,断言正确,ok为true且将值赋值给b;
  • value.(type) type是关键字,这种用法只能在 switch语句中使用;可以这样直接赋值v := value.(type),在case中就可以直接使用v了,避免多次类型断言;

反射

1. 变量的内在机制

参考文档: Golang反射

  • 类型信息,这部分是元信息,是预先定义好的;
  • 值信息,这部分是程序运行过程中,动态改变的;

2. 反射与空接口

  • A. 空接口可以存储任何类型的变量;
  • B.那么给你一个空接口,怎么知道里面存储的是什么类型的变量?
  • C. 在运行时动态的获取一个变量的类型信息和值信息,就叫反射;

3. 怎么分析?

  • A.内置包 reflect;
  • B. 获取类型信息: reflect.TypeOf
  • C. 获取值信息: reflect.ValueOf

获取一个空接口的变量类型

package main

import (
    `fmt`
    `reflect`
)

func reflectExample(a interface{})  {
    t := reflect.TypeOf(a)
    fmt.Println("a的类型是:", t)  // 打印:a的类型是: float64
}

func main() {
    var x float64 = 3.1415926
    reflectExample(x)
}

4. 常用类型的变量类型分析

reflect.ValueOf(a).Type()reflect.TypeOf(a)功能相同

package main

import (
    `fmt`
    `reflect`
)

func reflectType(a interface{})  {
    t := reflect.TypeOf(a)
    k := t.Kind()
    fmt.Println("a的类型是:", t.String(), k.String())  // 打印:a的类型是: float64
}

func reflectValue(a interface{})  {
    v := reflect.ValueOf(a)
    t := v.Type()            //  reflect.ValueOf(a).Type() 和 reflect.TypeOf(a) 功能相同
    // k := v.Kind()

    t1 := reflect.TypeOf(a)
    t1.Kind()

    fmt.Println("a的类型是:", t.String())

    switch t.Kind() {
    case reflect.Int:
        fmt.Println("a is a int. ")
    case reflect.Float32, reflect.Float64:
        fmt.Println("a is a Float.")
    case reflect.String:
        fmt.Println("a is a String.")
    default:
        fmt.Println("other type.")
    }
}


func main() {
    var x = 3.1415926
    reflectType(x)
    reflectValue(x)
}

5. 通过反射获取结构体的字段值信息和类型信息

package main

import (
    `fmt`
    `reflect`
)

type Student struct {
    Name string
    Age  int
    Sex  string
}

func main() {
    var stu = Student{
        Name: "caigy",
        Age:  18,
        Sex:  "male",
    }

    // 类型信息是静态的,在编译时就确定了;
    // 值信息是动态的,在运行时才能确定
    v := reflect.ValueOf(stu)
    t := v.Type()
    fmt.Println("stu的类型:", t.Kind().String())   // 变量类型为struct

    switch t.Kind() {
    case reflect.String:
        fmt.Println("is string.")
    case reflect.Struct:
        fmt.Println("is struct.")
        fmt.Println("结构体字段个数:", v.NumField()) // t.NumField()获取结构体字段数量,输出3

        for i := 0; i < v.NumField(); i++ {
            fmt.Println("type: ", t.Field(i),t.Field(i).Type) // t.Field(i) 通过下标获取字段的类型信息,
            fmt.Println("value: ", v.Field(i), v.Field(i).Type())  // v.Field(i) 通过下标获取字段的值信息,
        }
        fmt.Println("name: ", v.FieldByName("Name"))  // v.FieldByName("Name")根据字段名称获取字段的值
        fmt.Printf("struct value: %+v, type: %T\n", v.Interface(), v.Interface())  // {Name:caigy Age:18 Sex:male}, type: main.Student
        name := v.FieldByName("Name").Interface()
        fmt.Printf("name: %T, %+v\n", name, name)  // name: string, caigy
    }
}
  • v.Type() 返回变量stu的类型信息;
  • v.Kind() 返回变量stu的类型,为struct;
  • v.Field(i) 通过下标获取一个字段的值信息,返回的类型是reflect.Value
  • v.Field(i).Type()返回字段的值类型;
  • v.FieldByName("Name") 通过字段名称获取字段的值信息;
  • v.FieldByName("Name").Interface()获取指定字段的值,如Name,获取到的值为caigy,值类型为string;
  • v.Interface() 返回结构所有字段;
  • t.Field(i)通过下标获取字段的类型信息,返回的类型是reflect.StructField;
  • t.Field(i).Type 返回结构体字段类型对象,通过t.Field(i).Type.Kind()获取字段具体类型;

6. 通过反射给结构体字段赋值

注意:通过反射给结构体字段赋值,reflect.ValueOf(&stu) 必须传入指针类型&stu

package main

import (
    `fmt`
    `reflect`
)

type Student struct {
    Name string
    Age  int
    Sex  string
}

func main() {
    var stu Student
    v := reflect.ValueOf(&stu)
    v.Elem().FieldByName("Name").SetString("caigy")
    v.Elem().FieldByName("Age").SetInt(18)
    v.Elem().FieldByName("Sex").SetString("male")

    fmt.Printf("Student: %+v", stu)  // Student: {Name:caigy Age:18 Sex:male}
}
  • v.Elem() 返回指针v指向的值(内存),所以要想给结构体字段赋值必需这样使用:v.Elem().FieldByName("Name").SetString("caigy")

7. 获取结构体方法的类型信息

方法的类型信息是静态信息,编译时已经确定好了,所以应该用reflect.TypeOf(&stu)或者reflect.ValueOf(&stu).Type() 获取得方法的类型信息

package main

import (
    `fmt`
    `reflect`
)

type Student struct {
    Name string
    Age  int
    Sex  string
}

func (stu *Student) SetName(name string) {
    stu.Name = name
}

func (stu *Student) PrintVal() {
    fmt.Println(stu.Name, stu.Age, stu.Sex)
}

func (stu *Student) InitVal() {
    stu.Name = "CaiGY"
    stu.Age = 18
    stu.Sex = "Male"
}

func main() {
    var stu Student
    v := reflect.ValueOf(&stu)
    t := v.Type()

    fmt.Println(t.NumMethod()) // 返回Student的方法个数
    for i := 0; i < t.NumMethod(); i++ {
        method := t.Method(i)
        fmt.Printf("num: %d, methodName: %+v, methodSign: %+v\n", i, method.Name, method.Type)
        // 打印结果如下:
        // num: 0, methodName: InitVal, methodSign: func(*main.Student)
        // num: 1, methodName: PrintVal, methodSign: func(*main.Student)
        // num: 2, methodName: SetName, methodSign: func(*main.Student, string)
    }

    printVal, ok := t.MethodByName("PrintVal")
    // initVal, ok := t.MethodByName("InitVal")
    if !ok {
        fmt.Println("not ok.")
        return
    }
    fmt.Println("printVal.Type: ", printVal.Type)       // 返回方法类型:func(*main.Student)
}   
  • t.NumMethod() 返回方法的个数;
  • t.Method(i) 通过下标获取一个方法对象(reflect.Method),
  • printVal, ok := t.MethodByName("PrintVal") 通过名称获取方法对象(reflect.Method);
  • t.Method(i).Name 返回访求的名称;
  • t.Method(i).Type 返回访求的签名,如SetName()方法的签名:func(*main.Student, string);

8. 通过反射调用结构体的方法

调用结构体的方法, 是运行时确定的,所以要通过值类型来获取调用: reflect.ValueOf(&stu)

package main

import (
    `fmt`
    `reflect`
)

type Student struct {
    Name string
    Age  int
    Sex  string
}

func (stu *Student) SetName(name string) {
    stu.Name = name
}

func (stu *Student) PrintVal() {
    fmt.Println(stu.Name, stu.Age, stu.Sex)
}

func (stu *Student) InitVal() {
    stu.Name = "CaiGY"
    stu.Age = 18
    stu.Sex = "Male"
}

func main() {
    var stu Student
    v := reflect.ValueOf(&stu)

    printVal := v.MethodByName("PrintVal")
    initVal := v.MethodByName("InitVal")

    // 无参方法的调用
    var arg []reflect.Value
    initVal.Call(arg)   // 需要传入一个空数组:[]reflect.Value
    printVal.Call(arg)  // CaiGY 18 Male

    // 有参方法的调用:
    setName := v.MethodByName("SetName")
    // 1. 先构造方法的参数
    var args []reflect.Value
    name := reflect.ValueOf("CaiGuangyin")
    args = append(args, name)
    // 2. 通过Call()传入第1步构造的参数来调用SetName方法
    setName.Call(args)

    fmt.Printf("student: %+v\n", stu)   // student: {Name:CaiGuangyin Age:18 Sex:Male}
}

9. 通过反射获取结构体字段的tag信息

tag属于静态信息,保存在字段的类型信息里面,所以要用 reflect.ValueOf(stu).Type()或者reflect.TypeOf(stu)来取得变量的类型信息

package main

import (
    `fmt`
    `reflect`
)

type Student struct {
    Name string `json:"name" db:"meicai"`
    Age int `json:"age" db:"hehe"`
}

func main() {
    var stu Student
    v := reflect.ValueOf(stu)
    t := v.Type()

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        tag := field.Tag
        fmt.Println("json: ", tag.Get("json"))      // 获取tag名为json的值
        fmt.Println("db: ", tag.Get("db"))      // 获取tag名为db的值
    }

    // 通过 tag.Get(key)获取tag的值,如果key不存在时,则默认返回空字符串
    // 通过 tag.Lookup(key)获取tag的值时会返回值本身和一个bool值,可通过bool值来判断key是否存在
    name, ok := t.FieldByName("Name")
    if !ok {
        fmt.Println("field name is not exists.")
        return
    }
    jsonVal, ok := name.Tag.Lookup("json")
    if ! ok {
        fmt.Println("tag json is not exists.")
        return
    }
    fmt.Println("json tag value: ", jsonVal)    //json tag value:  name
}
  • t.Field(i).Tag 返回结构体字段的tag信息
    tag.Get(key)获取指定key的tag值,key不存在时,返回空字符串;
  • tag.Lookup(key)获取指定key的tag值,会返回两个值,key不存在时,返回的第二个值为false.

相关文章

  • Go语言之类型断言与反射

    目录 小记 make与new的区别 make()用来分配引用类型的内存,比如map、slice以及channeln...

  • Go语言类型转换和类型断言

    Go语言的类型转换和类型断言: 类型转换在编译期完成,包括强制转换和隐式转换 类型断言在运行时确定,包括安全类型断...

  • Go语言类型断言简述

    参考:http://c.biancheng.net/view/4281.html 关键点: 类型断言是组什么的?用...

  • go语言的类型断言

    一、基本介绍 类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,具体的如下: ...

  • 第04天(面对对象编程)_04

    16_接口的继承.go 17_接口转换.go 18_空接口.go 19_类型断言:if.go 20_类型断言:sw...

  • go语言中的类型断言与类型转换

    在读他人源码的时候碰到一个自己没读懂的写法: if r,ok:=w.(io.Reader);ok{r.Read(&...

  • Go语言 类型转换,类型断言,类型开关

    类型转换Go语言中提供了一种不同类型但是相互兼容的可以相互转换的方式,这种方式是非常有用且安全的。非数值间相互转换...

  • GO语言之类型断言

    概述 类型断言在GO中指的就是判断接口型变量运行时的实际类型.例如: 上面的例子中,变量f声明是接口类型,实际的类...

  • 17.Go语言·类型断言

    main.go interface.go

  • 初识Go语言-1

    Go语言学习路径 初识Go语言 Go语言环境搭建与IDE安装 Go语言基础语法 Go语言数据类型 Go语言变量和常...

网友评论

      本文标题:Go语言之类型断言与反射

      本文链接:https://www.haomeiwen.com/subject/ubtuwhtx.html