• Golang反射修改变量值


    1. 前言

    前面的随笔Golang反射获取变量类型和值分享了如何通过反射获取变量的类型和值,

    也就是Golang反射三大定律中的前两个,即从interface{}到反射对象和从反射对象到interface{}

    这篇随笔主要分享通过反射修改各种类型变量值的方法。

     

    2. 判断是否可修改

    reflect提供func (v Value) CanSet() bool判断对象值是否修改。

    一般情况下,通过反射修改变量值,需要满足以下两个条件。

     

    2.1 该值是可寻址的

    类似函数传参,如果需要在函数内修改入参数的内容,那么就需要传引用,而不是传值。

    函数内修改入参指向的内容,才能将修改效果“带出”该函数的作用域。

    同理,反射修改变量的值,应当是可以寻址的,修改的是反射对象指向的数据内容,

    因此,通过反射函数func ValueOf(i any) Value

    • 入参i是引用时,i指向的内容可寻址,因此返回参数Value不可修改,Value.Elem可修改。
    • 入参i是地址时,返回参数Value可修改。
    • 入参i是引用地址时,返回参数ValueValue.Elem均可修改。

    上述三种情况如下图所示,经过寻址的内容才有可能是可修改的。

     

    2.2 该值是可导出的

     这个主要是针对结构体的成员,该成员的字段名的首字母需要是大写,即是“public”的。

     

    3. 修改slice

    slice是引用类型,slice的数据结构如下图所示,通过反射可以修改slice指向的内容。

    修改指定下标的数据内容,并且数据类型需要和修改前一只,否则会panic

    func main() {
    s := []int{1, 2, 3}
    valueS := reflect.ValueOf(s)
    // slice 是否可修改 不可整体修改
    fmt.Printf("valueS Kind:%v CanSet:%v Index(0).CanSet:%v\n", valueS.Kind(), valueS.CanSet(), valueS.Index(0).CanSet())
    // 修改指定下标的元素值
    valueS.Index(0).Set(reflect.ValueOf(10))
    valueS.Index(1).SetInt(20)
    fmt.Printf("after edit:%v\n", s)
    // panic: reflect: call of reflect.Value.SetFloat on int Value
    //valueS.Index(1).SetFloat(100)
    }

    代码输出如下

    $ go run main.go
    valueS Kind:slice CanSet:false Index(0).CanSet:true
    after edit:[10 20 3]

    如果需要整体修改修改slice,那么需要传入slice的地址

    func main() {
    s := []int{1, 2, 3}
    // slice的指针
    valuePtrS := reflect.ValueOf(&s)
    fmt.Printf("valuePtrS kind:%v CanSet:%v\n", valuePtrS.Kind(), valuePtrS.CanSet())
    // 获取指针指向的内容
    valueS := valuePtrS.Elem()
    fmt.Printf("valueS kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet())
    // 整体修改slice
    valueS.Set(reflect.ValueOf([]int{4, 5, 6, 7}))
    fmt.Printf("replace edit:%v\n", s)
    }

    代码输出如下

    $ go run main.go
    valuePtrS kind:ptr CanSet:false
    valueS kind:slice CanSet:true
    replace edit:[4 5 6 7]

     

    4. 修改array

    array不是引用类型,因此func ValueOf(i any) Value需要传入array的地址。

    func main() {
    s := [3]int{1, 2, 3}
    // array的指针
    valuePtrS := reflect.ValueOf(&s)
    fmt.Printf("valuePtrS kind:%v CanSet:%v\n", valuePtrS.Kind(), valuePtrS.CanSet())
    // 获取指针指向的内容
    valueS := valuePtrS.Elem()
    fmt.Printf("valueS kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet())
    // 修改指定下标数据
    valueS.Index(0).SetInt(10)
    fmt.Printf("after edit:%v\n", s)
    // 整体修改slice
    valueS.Set(reflect.ValueOf([3]int{4, 5, 6}))
    fmt.Printf("replace edit:%v\n", s)
    //panic: reflect.Set: value of type [4]int is not assignable to type [3]int
    //valueS.Set(reflect.ValueOf([4]int{4, 5, 6}))
    }

    代码输出如下

    $ go run main.go
    valuePtrS kind:ptr CanSet:false
    valueS kind:array CanSet:true
    after edit:[10 2 3]
    replace edit:[4 5 6]

     

    5. 修改结构体

    带修改的结构体的成员的字段名首字母需要大写。

    func main() {
    type myStruct struct {
    Num int `json:"num_json" orm:"column:num_orm"`
    Desc string `json:"desc_json" orm:"column:desc_orm"`
    }
    s := myStruct{
    Num: 1,
    Desc: "desc",
    }
    valueS := reflect.ValueOf(&s)
    // 指针本身不可修改 可指向的内容
    fmt.Printf("Kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet())
    // 获取指针指向的内容
    valueS = valueS.Elem()
    fmt.Printf("Kind:%v CanSet:%v Field(0).CanSet:%v\n", valueS.Kind(), valueS.CanSet(), valueS.Field(0).CanSet())
    // 修改指定成员的值
    valueS.Field(0).SetInt(10)
    fmt.Printf("after edit:%+v\n", s)
    // 替换整体内容
    valueS.Set(reflect.ValueOf(myStruct{Num: 100, Desc: "new desc"}))
    fmt.Printf("after replace:%+v\n", s)
    }

    代码输出如下,

    $ go run main.go
    Kind:ptr CanSet:false
    Kind:struct CanSet:true Field(0).CanSet:true
    after edit:{Num:10 Desc:desc}
    after replace:{Num:100 Desc:new desc}

     

    6. 修改map

    反射通过func (v Value) SetMapIndex(key, elem Value)修改map指定keyvalue

    func main() {
    m := map[int]string{
    1: "1",
    2: "2",
    3: "3",
    }
    valueM := reflect.ValueOf(m)
    // 迭代器访问
    iter := valueM.MapRange()
    for iter.Next() {
    fmt.Printf("key:%v val:%v\n", iter.Key(), iter.Value())
    // 将所有value修改为"a"
    valueM.SetMapIndex(iter.Key(), reflect.ValueOf("a"))
    }
    fmt.Println("--- after edit ---")
    // 通过key访问
    keys := valueM.MapKeys()
    for i := 0; i < len(keys); i++ {
    fmt.Printf("key:%v val:%v\n", keys[i], valueM.MapIndex(keys[i]))
    }
    }

    代码输出如下

    $ go run main.go
    key:1 val:1
    key:2 val:2
    key:3 val:3
    --- after edit ---
    key:1 val:a
    key:2 val:a
    key:3 val:a

     

     

     

  • 相关阅读:
    2022年全国大学生数学建模竞赛C题思路
    Spring Data JPA想要学得好,缓存机制掌握好
    浅谈路由器基本结构与工作原理
    电影兑换券的推荐策略——二分图最优匹配算法
    嵌入式编程别忽略了C语言的标准
    03excel函数2
    Vue2 零基础入门 Vue2 零基础入门第四天 4 生命周期 & 数据共享
    (248)Verilog HDL:奇偶校验
    基于支持向量机的网络⼊侵检测系统的全面调查和分类
    JavaScrip练习
  • 原文地址:https://www.cnblogs.com/amos01/p/16930711.html