【Go编程学习】反射机制

反射是什么

反射是指程序可以访问、检测和修改它本身状态或行为的一种能力。

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

反射的实现

Go的反射基础是接口和类型系统,Go的反射机制是通过接口来进行的。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

反射三定律

1.反射可以将“接口类型变量”转换为“反射类型对象”。

变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。

package main

import (
	"fmt"
	"reflect"
)

func main() {
    
    
	var Num float64 = 3.14
	//func TypeOf(i interface{}) Type
	//func ValueOf(i interface{}) Value
	v := reflect.ValueOf(Num)
	t := reflect.TypeOf(Num)

	fmt.Println("Reflect : Num.Value = ", v)
	fmt.Println("Reflect : Num.Type  = ", t)
}

上面的例子通过reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型对象v和t,v是Num的值,t也是Num的类型。

这两个函数的参数类型都是空接口,在整个过程中,当我们调用reflect.TypeOf(x)的时候,当我们调用reflect.TypeOf(x)的时候,Num会被存储在这个空接口中,然后reflect.TypeOf再对空接口进行拆解,将接口类型变量转换为反射类型变量

2.反射可以将“反射类型对象”转换为“接口类型变量”。

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。

package main
import (
    "fmt"
    "reflect"
)
func main() {
    
    
    var Num = 3.14
    v := reflect.ValueOf(Num)
    t := reflect.TypeOf(Num)
    fmt.Println(v)
    fmt.Println(t)

    origin := v.Interface().(float64)
    fmt.Println(origin)
}
//3.14
//float64
//3.14

3.如果要修改“反射类型对象”,其值必须是“可写的”。

package main
import (
    "reflect"
)
func main() {
    
    
        var Num float64 = 3.14
        v := reflect.ValueOf(Num)
        v.SetFloat(6.18)
}

上述程序中,reflect.ValueOf(x)函数通过传递一个Num拷贝创建了v,反射对象v包含的是副本值,所以无法修改。

我们可以通过CanSet函数来判断反射对象是否可以修改,如下:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    
    
    var Num float64 = 3.14
    v := reflect.ValueOf(Num)
    fmt.Println("v的可写性:", v.CanSet())
}

要想 v 的更改能作用到 Num,那就必须传递 Num 的地址 v = reflect.ValueOf(&Num)。

通过 Type () 我们看到 v 现在的类型是 *float64 并且仍然是不可设置的。

实际上,所有通过 reflect.ValueOf(x) 返回的 reflect.Value 都是不可取地址的。

要想让其可设置我们需要使用 Elem() 函数,它是解引用方式生成的,指向另一个变量,因此是可取地址的。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    
    
    var Num float64 = 3.14
    v := reflect.ValueOf(Num)
    // setting a value:
    // v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
    fmt.Println("settability of v:", v.CanSet())
    v = reflect.ValueOf(&Num) // Note: take the address of x.
    fmt.Println("type of v:", v.Type())
    fmt.Println("settability of v:", v.CanSet())
    v = v.Elem()
    fmt.Println("The Elem of v is: ", v)
    fmt.Println("settability of v:", v.CanSet())
    v.SetFloat(3.1415) // this works!
    fmt.Println(v.Interface())
    fmt.Println(v)
}
/*
settability of v: false
type of v: *float64
settability of v: false
The Elem of v is:  3.14
settability of v: true
3.1415
3.1415
*/

小结

1.反射对象包含了接口变量中存储的值以及类型。

2.如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值;

3.如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),则该反射对象不可以修改。

反射的作用

1.在编写不定传参类型函数的时候,或传入类型过多时

典型应用是对象关系映射

type User struct {
    
    
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // set field size to 255
  MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
  Num          int     `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
  Address      string  `gorm:"index:addr"` // create index with name `addr` for address
  IgnoreMe     int     `gorm:"-"` // ignore this field
}

var users []User
db.Find(&users)

2.不确定调用哪个函数,需要根据某些条件来动态执行

func bridge(funcPtr interface{
    
    }, args ...interface{
    
    })

第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数。

反射的实例

1.通过反射修改内容

如上一部分介绍的,通过Elem()操作实现

2.通过反射调用方法

package main

import (
        "fmt"
        "reflect"
)

func hello() {
    
    
  fmt.Println("Hello world!")
}

func main() {
    
    
  hl := hello
  fv := reflect.ValueOf(hl)
  fv.Call(nil)
}

反射会使得代码执行效率较慢,原因有

1.涉及到内存分配以及后续的垃圾回收

2.reflect实现里面有大量的枚举,也就是for循环,比如类型之类的

参考

https://github.com/datawhalechina/go-talent

Go入门指南

猜你喜欢

转载自blog.csdn.net/i0o0iW/article/details/111651516