Go 语言入门与进阶:通过反射查看和修改变量的值

697 阅读3分钟

这是我参与更文挑战的第 22 天,活动详情查看: 更文挑战

前文回顾

如果你还没有 Go 语言基础,建议阅读我的 从零学 Go

本系列文章,我将会进一步加深对 Go 语言的讲解,更一步介绍 Go 中的包管理、反射和并发等高级特性。 前面一篇文章主要介绍了 Go 反射 reflect.StructField 和 reflect.Method 相关的内容。本文将会介绍 reflect.Value,通过反射查看和修改变量的值。

reflect.Value 反射值对象

使用 Type 类型对象可以获取到变量的类型与种类,但是无法获取到变量的值和对值进行修改,这与我们使用反射的目的还相差一大截。这时候就需要使用 reflect#ValueOf 获取反射变量的实例信息 Value,通过 Value 对变量的值进行查看和修改。获取一个变量 Value 的代码如下所示:

	name := "小明"
	valueOfName := reflect.ValueOf(name)
	v
	fmt.Println(valueOfName.Interface())

预期是输出结果如下:

小明

在上述代码中,我们通过 reflect#ValueOf 获取到了 name 变量的 Value 对象,并通过 valueOfName#Interface 获取到 name 变量的值。除了通过 Value#Interface 方法获取变量的值,Value 中还提供了用于获取变量原值的方法:

func (v Value) Interface() (i interface{})
// 将值以 int 返回
func (v Value) Int() int64
// 将值以 float 返回
func (v Value) Float() float64
// 将值以 []byte 返回
func (v Value) Bytes() []byte
// 将值以 string 返回
func (v Value) String() string
// 将值以 bool 返回
func (v Value) Bool() bool

如果取值变量的原类型是与取值的方式不匹配,那么程序就会 panic,如下例子所示:

	name := "小明"
	valueOfName := reflect.ValueOf(name)
	fmt.Println(valueOfName.Bytes())

上述代码中我们尝试将 string 类型的值以 []byte 就会抛出以下错误:

panic: reflect: call of reflect.Value.Bytes on string Value

因此在不清楚取值变量的具体类型时,建议使用 Value#Interface 方法取值,再通过类型推导进行赋值。

除此之外,还可以通过变量的 Type 对象,使用 reflect#New 方法构建一个相同类型的新变量,值以 Value 对象的形式返回,代码如下所示:

   typeOfHero := reflect.TypeOf(Hero{})
	heroValue := reflect.New(typeOfHero)
	fmt.Printf("Hero's type is %s, kind is %s\n", heroValue.Type(), heroValue.Kind())

预期结果为:

Hero's type is *main.Hero, kind is ptr

相当于是使用 new(Hero) 构造了一个新的 Hero。

对变量的修改可以通过 Value#Set 方法实现,如下例子所示:

	name := "小明"
	valueOfName := reflect.ValueOf(&name)
	valueOfName.Elem().Set(reflect.ValueOf("小红"))
	fmt.Println(name)

代码的预期结果是 name 被修改为小红:

小红

可以注意到,上面代码中,reflect#ValueOf 获取的是 name 指针的 Value,再通过 #Elem 获取指针 Value 的 Value 用于修改 name 的值。在 Go 中,如何直接通过 reflect#ValueOf 获取的 Value 都无法直接进行设值,因为 reflect#ValueOf 方法处理的都是值类型,即使是 &name 也是处理的指针的拷贝,获取到的 Value 无法对原来的变量进行取址,所以直接设值会出现错误。而上述例子中通过 #Elem 对 valueOfName 进行解引用获取的 Value 具备指向原有变量的指针,因此是可寻址可设置的。

一个变量的 Value 是否可寻址可以通过 #CanAddr 方法判断,代码如下所示:

	name := "小明"
	valueOfName := reflect.ValueOf(name)
	fmt.Printf( "name can be address: %t\n", valueOfName.CanAddr())
	valueOfName = reflect.ValueOf(&name)
	fmt.Printf( "&name can be address: %t\n", valueOfName.CanAddr())
	valueOfName = valueOfName.Elem()
	fmt.Printf( "&name's Elem can be address: %t", valueOfName.CanAddr())

预期输出为:

name can be address: false
&name can be address: false
&name's Elem can be address: true

从结果可以看到,只有指针类型解引用后的 Value 才是可寻址的。

小结

本文主要介绍了 Go 语言的反射 reflect.Value,通过反射查看和修改变量的值。下一篇文章将会继续通过案例来介绍 Go 语言的反射 reflect.Value ,如何判断一个变量的 Value 是否可设,调用函数等功能。

阅读最新文章,关注公众号:aoho求索