Go语言之类型断言与反射

CaiGuangyin · · 1566 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

目录

小记
    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.

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:CaiGuangyin

查看原文:Go语言之类型断言与反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1566 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传