欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

golang 如何用反射reflect操作结构体

程序员文章站 2022-03-16 10:07:56
背景需要遍历结构体的所有field对于exported的field, 动态set这个field的value对于unexported的field, 通过强行取址的方法来获取该值(tricky?)...

背景

需要遍历结构体的所有field

对于exported的field, 动态set这个field的value

对于unexported的field, 通过强行取址的方法来获取该值(tricky?)

思路

下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多

simplestrtuctfield 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针。然后解引用。

接下来遍历结构体的每个field, exported字段是caninterface的,对于unexported字段,需要强行取址来获取其值

model.go

package model
type person struct {
 name string
 age  int
}
func newperson(name string, age int) *person {
 return &person{
  name: name,
  age:  age,
 }
}

main.go

package main
import (
	"github.com/miaomiao3/log"
	"../model"
	"reflect"
	"unsafe"
)
func main() {
	person := model.newperson("haha", 12)
	log.debug("before:%+v", person)
	simplestrtuctfield(person)
	simplestrtuctfield(person)
	log.debug("after:%+v", person)
}
// get unexported field
func simplestrtuctfield(v interface{}) {
	datatype := reflect.typeof(v)
	datavalue := reflect.valueof(v)
	if datatype.kind() == reflect.ptr {
		if datavalue.isnil() {
			panic("nil ptr")
		}
		// 如果是指针,则要判断一下是否为struct
		origintype := reflect.valueof(v).elem().type()
		if origintype.kind() != reflect.struct {
			return
		}
		// 解引用
		datavalue = datavalue.elem()
		datatype = datatype.elem()
	} else {
		panic("non ptr")
	}
	num := datatype.numfield()
	for i := 0; i < num; i++ {
		field := datatype.field(i)
		fieldname := field.name
		fieldvalue := datavalue.fieldbyname(fieldname)
		if !fieldvalue.isvalid() {
			continue
		}
		if fieldvalue.caninterface() {
			log.debug("exported fieldname:%v value:%v", fieldname, fieldvalue.interface())
			if fieldvalue.canset() && fieldvalue.kind() == reflect.string {
				oldvalue := fieldvalue.interface().(string)
				fieldvalue.setstring(oldvalue + " auto append")
			}
		} else {
			// 强行取址
			forcevalue := reflect.newat(fieldvalue.type(), unsafe.pointer(fieldvalue.unsafeaddr())).elem()
			log.debug("unexported fieldname:%v value:%v", fieldname, forcevalue.interface())
		}
	}
}

output:

2019/06/02 17:15:31.64 [d] before:&{name:haha age:12}

2019/06/02 17:15:31.64 [d] exported fieldname:name value:haha

2019/06/02 17:15:31.64 [d] unexported fieldname:age value:12

2019/06/02 17:15:31.64 [d] after:&{name:haha auto append age:12}

可以看到,name字段被反射改变了,age的值也已经获取到

补充:go语言通过反射创建结构体、赋值、并调用对应方法

看代码吧~

package main
import (
	"fmt"
	"reflect"
	"testing"
)
type call struct {
	num1 int
	num2 int
}
func (call call) getsub(name string){
	fmt.printf("%v 完成了减法运算,%v - %v = %v \n", name, call.num1, call.num2, call.num1 - call.num2)
}
func (call *call) getsum(name string){
	fmt.printf("%v 完成了加法运算,%v + %v = %v \n", name, call.num1, call.num2, call.num1 + call.num2)
}
func testreflect(t *testing.t) {
	var (
		call *call
		rvalues []reflect.value
		rvalues2 []reflect.value
	)
	ptrtype := reflect.typeof(call) //获取call的指针的reflect.type
	truetype := ptrtype.elem() //获取type的真实类型
	ptrvalue := reflect.new(truetype) //返回对象的指针对应的reflect.value
	call = ptrvalue.interface().(*call)
	truevalue := ptrvalue.elem() //获取真实的结构体类型
	truevalue.fieldbyname("num1").setint(123)//设置对象属性,注意这个一定要是真实的结构类型的reflect.value才能调用,指针类型reflect.value的会报错
	//ptrvalue.fieldbyname("num2").setint(23)
	truevalue.fieldbyname("num2").setint(23)
	//rvalues = make([]reflect.value, 0)
	rvalues = append(rvalues, reflect.valueof("xiaopeng"))//调用对应的方法
	fmt.println(rvalues)
	truevalue.methodbyname("getsub").call(rvalues)
	/*
	fixme 在反射中,指针的方法不可以给实际类型调用,实际类型的方法可以给指针类型调用,因为go语言对这种操作做了封装
	所以下面一句是没问题的
	下下一句会运行时报错
	 */
	//ptrvalue.methodbyname("getsub").call(rvalues)
	//truevalue.methodbyname("getsum").call(append(rvalues2, reflect.valueof("hiram")))
	ptrvalue.methodbyname("getsum").call(append(rvalues2, reflect.valueof("hiram")))
	fmt.println(call)
	
	/*
	fixme 在实际使用中  指针和实体都能相互转换,不会影响调用
	但是指针的方法在方法体内的操作会影响到结构体本身属性
	而实体的方法不会,因为go对于结构体、数组、基本类型都是值传递
	 */
	call.getsub("aaa")
	(*call).getsub("bbb")
	call.getsum("ccc")
	(*call).getsum("ddd")
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。