Golang:利用反射修改不同类型对象的值

引言

在写代码时,我们可能会有这样的需求:根据特定的对象类型进行定制操作

针对这件事情,C++中提供了type_traits这一机制。说的高大上一些,它是一种萃取机。实际上我认为type_traits就是利用模板的特化和模板参数推导,从而在编译期就获得的类模板的一个实例。

而接下来介绍Go一种比较有意思的机制:反射。相当特别的是,这件事是在运行期完成的。

借用李文周的博客中对反射的介绍:

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

可以看到,反射在为程序员提供便利时也带来了一些运行期的开销,这一点可能比较像C++中的RTTI技术。同样的,过多的反射会降低可执行程序的运行效率,我们应该慎用它

实现

这里只举了string和int的例子,具体讲解在注释中。

package main

import (
	"fmt"
	"reflect"
)

// TypeOf : 类型信息
// ValueOf : 值信息
func set_val(src interface{}, dst interface{}) bool {
	src_val := reflect.ValueOf(src)
	// 无法被设置通常代表其非指针
	if !src_val.CanSet() && !(src_val.Kind() == reflect.Ptr) {
		fmt.Printf("can't set %v\n", reflect.TypeOf(src).Kind())
		return false
	}

	// 到此处应是一个有效的Ptr
	src_elem := src_val.Elem()
	switch src_elem.Kind() {
	case reflect.String:
		src_elem.SetString(reflect.ValueOf(dst).String())
		fmt.Printf("string : %v in func set_val\n", src_elem.String())
	case reflect.Int:
		src_elem.SetInt(reflect.ValueOf(dst).Int())
		fmt.Printf("int : %v in func set_val\n", src_elem.Int())
	default:
		fmt.Printf("unknow type : in func set_val\n")
	}
	return true
}

// 修改反射对象
func main() {
	var (
		s string = "s"
		i int = 0
	)
	fmt.Println(i, s)
	// 无效的修改
	set_val(i, 666)
	set_val(s, "alter s")
	fmt.Println(i, s)
	// 有效的修改
	set_val(&s, "alter s")
	set_val(&i, 666)
	fmt.Println(i, s)
}

输出:

0 s
can't set int
can't set string
0 s
string : alter s in func set_val
int : 666 in func set_val
666 alter s

另外,当我尝试修改结构体成员时,我失败了,我找不到一个通用的方法使reflect.Value转换成实际类型。并且我发现传入一个reflect.Value类型时会出现不正确的结果(在例子中,对象被识别为struct),而在代码中调用Kind,显示又是正确的。这样的话可能就没法递归调用这个函数了。

代码如下:

package main

import (
	"fmt"
	"reflect"
)

type S struct {
	s1 string
	s2 string
}

// TypeOf : 类型信息
// ValueOf : 值信息
func set_val(src interface{}, dst interface{}) bool {
	src_val := reflect.ValueOf(src)
	dst_val := reflect.ValueOf(dst)
	// 无法被设置通常代表其非指针
	if !src_val.CanSet() && !(src_val.Kind() == reflect.Ptr) {
		fmt.Printf("can't set %v\n", reflect.TypeOf(src).Kind())
		return false
	}

	// 到此处应是一个有效的Ptr
	src_elem := src_val.Elem()
	// 获取结构体成员
	// 对非Ptr调用Elem会造成panic
	var dst_elem reflect.Value
	if dst_val.Kind() == reflect.Ptr {
		dst_elem = reflect.ValueOf(dst).Elem()
	} else {
		dst_elem = reflect.ValueOf(dst)
	}

	switch src_elem.Kind() {
	case reflect.String:
		src_elem.SetString(reflect.ValueOf(dst).String())
		fmt.Printf("string : %v in func set_val\n", src_elem.String())
	case reflect.Int:
		src_elem.SetInt(reflect.ValueOf(dst).Int())
		fmt.Printf("int : %v in func set_val\n", src_elem.Int())
	case reflect.Struct:
		for i := 0; i < src_elem.NumField(); i++ {
			src_field := src_elem.Field(i)
			dst_field := dst_elem.Field(i)
			//fmt.Println("Kind of src_field is", src_field.Kind())	// Kind of src_field is string
			// 递归
			set_val(src_field, dst_field)	// can't set struct
		}
		fmt.Printf("struct : %v in func set_val\n", src_elem)
	default:
		fmt.Printf("unknow type : in func set_val\n")
	}
	return true
}

// 修改反射对象
func main() {
	var (
		s string = "s"
		i int = 0
		s_struct S = S{"s1", "s2"}
	)

	fmt.Println(i, s, s_struct)
	// 无效的修改
	set_val(i, 666)
	set_val(s, "alter s")
	fmt.Println(i, s, s_struct)
	// 有效的修改
	set_val(&s, "alter s")
	set_val(&i, 666)
	set_val(&s_struct, S{"alter s1", "alter s2"})
	fmt.Println(i, s, s_struct)
}

输出:

0 s {s1 s2}
can't set int
can't set string
0 s {s1 s2}
string : alter s in func set_val
int : 666 in func set_val
can't set struct
can't set struct
struct : {s1 s2} in func set_val
666 alter s {s1 s2}

参考

Go语言基础之反射 | 李文周的博客
golang reflect - Go语言中文网 - Golang中文社区

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值