> 该文章始发于公众号【迈莫coding】
> 地址:[https://mp.weixin.qq.com/s/lgZykTL8ls6aG0OMNSbZMw](https://mp.weixin.qq.com/s/lgZykTL8ls6aG0OMNSbZMw)
# 目录
- 概念
- ValueOf使用格式
- 反射获取值对象(ValueOf)
- 通过canSet方法修改值
- 判断及获取元素的相关方法
- 值修改相关方法
- 值修改条件:可寻址
- 结构体值的非导出字段不能通过反射来修改
- 通过ValueOf访问成员对象
- 闲聊
- 欢迎加入我的公众号【迈莫coding】 一起pk大厂
# 概念
在程序运行期间,如果想要获取某个接口所代表的值,那么可以使用 reflect.ValueOf 进行获取,使用方式和 reflect.TypeOf 函数使用方式类型,都是传入一个interface{}接口的参数。当我们将一个接口值传递给一个 reflect.ValueOf 调用时,此调用返回的是代表着此接口值的动态值的一个reflect.Value值。 我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。
# ValueOf使用格式
当调用 reflect.ValueOf 函数时,他会返回一个 reflect.Value 对象,如果我们想要获取原值,则可通过具体方法来进行获取,具体方法如下:
| 方法名 | 说 明 |
|--|--|
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64| 将值以 int 类型返回,所有有符号整型均可以此方式返回|
| Uint() uint64|将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 |将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
|Bool() bool |将值以 bool 类型返回 |
|Bytes() []bytes |将值以字节数组 []bytes 类型返回 |
|String() string | 将值以字符串类型返回 |
**演示**
```go
package main
import (
"fmt"
"reflect"
)
func main() {
var a int = 56
value := reflect.ValueOf(a)
fmt.Println(value.Interface())
fmt.Println(value.Interface().(int))
}
```
**结果:**
`56`
**代码说明:**
```go
- 第9行:定义变量a并赋值为56
- 第10行:通过reflect.ValueOf函数获取值对象
- 第11行:获取值对象的原型值
- 第12行:通过断言获取原型值
```
# 通过ValueOf访问成员对象
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。
| 方 法 | 备 注 |
|--|--|
|Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机 |
| NumField() int |返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机 |
| FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
| FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机|
| FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机 |
**演示**
```go
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
Name string
Age int
}
func main() {
turbo := &Turbo{
Name: "迈莫coding",
Age: 1,
}
value := reflect.ValueOf(turbo)
if value.Kind() == reflect.Ptr {
value = value.Elem()
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fmt.Printf("字段类型:%v, 字段值:%v\n", field.Type(), field.Interface())
}
st := value.FieldByName("Name")
fmt.Printf("%v\n", st.Interface())
}
}
```
**结果:**
```go
字段类型:string, 字段值:迈莫coding
字段类型:int, 字段值:1
迈莫coding
```
**代码说明:**
```go
- 第8行:定义结构体Turbo
- 第14行:初始化Turbo结构体对象
- 第18行:通过reflect.ValueOf函数获取Value对象
- 第19行:判断反射类型对象种类是否为指针
- 第20行:获取指针指向的元素
- 第21行:遍历循环结构体中的字段属性
- 第22行:获取某个字段属性的Value对象
- 第23行:获取某个字段类型和字段原值
```
# 通过canSet方法修改值
一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的Go值是否可以被修改(可以被赋值)。 如果一个Go值可以被修改,则我们可以调用对应的 reflect.Value 值的Set方法来修改此Go值。 注意: reflect.ValueOf 函数直接返回的 reflect.Value 值都是不可修改的。
## 判定及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
| 方法名 | 备 注 |
|--|--|
| Elem() Value | 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value |
| Addr() Value | 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 |
| CanAddr() bool | 表示值是否可寻址 |
| CanSet() bool |返回值能否被修改。要求值可寻址且是导出的字段 |
## 值修改相关方法
使用 reflect.Value 修改值的相关方法如下表所示。
| Set(x Value) | 将值设置为传入的反射值对象的值 |
|--|--|
|Setlnt(x int64) |使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机 |
| SetUint(x uint64) | 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机|
| SetFloat(x float64) | 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机 |
| SetBool(x bool) | 使用 bool 设置值。当值的类型不是 bod 时会发生宕机 |
| SetBytes(x []byte) | 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机 |
| SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机|
## 值修改条件:可寻址
通过反射修改变量值的前提条件之一:这个值必须可以被寻址。简单地说就是这个变量必须能被修改。
- **可寻址,可被修改**
```go
package main
import (
"fmt"
"reflect"
)
func main() {
n := "迈莫coding"
str := &n
value := reflect.ValueOf(str)
fmt.Printf("返回值是否被修改?%v, \n表示值是否可寻址?%v\n", value.CanSet(), value.CanAddr())
vn := value.Elem()
fmt.Printf("返回值是否被修改?%v, \n表示值是否可寻址?%v\n", value.CanSet(), value.CanAddr())
vn.Set(reflect.ValueOf("迈莫coding正式运营一个月"))
fmt.Println(n)
}
```
**结果:**
```go
返回值是否被修改?false,
表示值是否可寻址?false
返回值是否被修改?false,
表示值是否可寻址?false
迈莫coding正式运营一个月
```
**代码说明:**
```go
- 第9行:定义字符串n
- 第11行:通过reflect.ValueOf函数获取Value对象
- 第13行:取得vp的底层指针值引用的值的代表值
- 第15行:通过set()方法设置新值
```
- **非指针地址数据不可被修改**
```go
package main
import (
"fmt"
"reflect"
)
func main() {
var a int = 666
value := reflect.ValueOf(a)
value.SetInt(888)
fmt.Println(a)
}
```
**结果:**
```go
panic: reflect: reflect.Value.SetInt using unaddressable value
```
通过反射对非指针数据进行修改时,会抛出异常。
- **结构体值的非导出字段不能通过反射来修改**
```go
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
Name interface{}
age interface{}
}
func main() {
vs := reflect.ValueOf(&Turbo{})
vs = reflect.Indirect(vs)
vx, vy := vs.Field(0), vs.Field(1)
fmt.Println(vx.CanSet(), vx.CanAddr())
fmt.Println(vy.CanSet(), vy.CanAddr())
vb := reflect.ValueOf(123)
vx.Set(vb)
vy.Set(vb) // 会造成恐慌,因为vy代表的值是不可修改的。
fmt.Println(vx.IsNil(), vy.IsNil())
}
```
**结果:**
```go
true true
false true
panic: reflect: reflect.Value.Set using value obtained using unexported field
false true
```
**代码说明:**
```go
- 第8行:定义一个结构体类型Turbo
- 第14行:如果vs代表着一个指针,下一行等价于"vs := vs.Elem()"
- 第15行:分别取出结构体对象中的字段属性值
- 第17行:vy为是地址类型但不可被修改
- 第19行:判断vx,vy是否为空值
- 第20行:vx代表的值是可修改的,可暴露的字段
- 第21行:vy代表的值是不可修改的,会Panic异常
```
# 闲聊
- 读完文章,自己是不是和反射的cp率又提高了
- 我是迈莫,欢迎大家和我交流
> 觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~
# 欢迎加入我的公众号【迈莫coding】 一起pk大厂
- **迈莫coding欢迎客官的到来**
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112120323563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDY2MDY2,size_16,color_FFFFFF,t_70#pic_center)
有疑问加站长微信联系(非本文作者))