3.24 Go之反射规则
静态类型语言
package main
import "fmt"
/*
探讨Go的规则
*/
func main() {
// 声明一个类型
type MyInt int
// 声明一个int类型变量和一个MyInt类型变量
var a int
var b MyInt
// 赋值
b = 1
a = b
fmt.Println(a, b)
}
分析:
-
首先这个赋值是不允许的,编译器报错了.因为编译器认为变量
b
是MyInt
类型,虽然MyInt
类型的基本类型是Int
-
要想成功的赋值,只能强制给变量
b
转型
赋值成功的写法:
package main
import "fmt"
/*
探讨Go的规则
*/
func main() {
// 声明一个类型
type MyInt int
// 声明一个int类型变量和一个MyInt类型变量
var a int
var b MyInt
// 赋值
b = 1
a = int(b)
fmt.Println(a, b)
}
反射的关键:接口
接口的特点:
-
一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身)
-
由
Go
语言的特点可以举例说明:-
一个类型声明实现了
Reader
或(Writer
)方法那么它就实现了io.Reader
或(io.Writer
)接口,那么如果一个变量声明为io.Reader
类型就可以持有任何一个实现了Reader
方法的类型的值
-
var r io.Reader
r = os.Stdin
分析:
-
os.Stdin
实现了io.Reader
接口下的Reader
方法 -
r
变量声明为io.Reader
类型,那么即便在赋值的时候r
被赋值为os.Stdin
类型的值在Go
的编译看来它也是io.Reader
类型
-
空接口--->表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。
Go反射定律
可以将“接口类型变量”转换为“反射类型对象”
reflect
包下的两种类型:
-
Type
-
Value
对应的函数:
-
reflect.TypeOf
-
reflect.ValueOf
reflect.TypeOf
的函数签名里包含一个空接口
调用reflect.TypeOf(x)
时,x
被存储在一个空接口变量中被传递过去,然后reflect.TypeOf
对空接口变量进行拆解,恢复其类型信息
其他的一些函数:
-
reflect.Value
有一个方法Type()
,返回一个reflect.Type
类型的对象。 -
Type
和Value
有一个Kind
方法,返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice
等。
反射库中提供的修改数据的方法:
-
SetInt
-
SetFloat
涉及概念:
-
可修改性(
settability
)
可修改性的特点:
Value
的getter
和setter
方法为了保证API
的精简性在取值的时候没有精确到很细致的类型--->比如int
类型在取值时是int64
类型
反射对象的Kind
方法描述的是基础类型,而不是静态类型。
可以将“反射类型对象”转换为“接口类型变量”
一个reflect.Value
类型的变量可以使用interface
方法恢复其接口类型的值.
函数声明:
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
通过断言,恢复底层的具体值:
y := v.Interface().(float64)
fmt.Println(y)
fmt.Println
和fmt.Printf
两个函数都会接收空接口变量作为参数.fmt
包内部会对接口变量进行拆包.故可以:
fmt.Println(v.Interface())
不需要对v.Interface()
的结果进行类型断言,空接口值内部包含了具体值的类型信息,Printf
函数会恢复类型信息。
Interface
方法和ValueOf
函数作用相反,相同的是返回值的静态类型是interface{}
要修改“反射类型对象”其值必须是“可写的”
示例代码:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
问题不在于值7.1
不能被寻址,而是因为变量v
是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。
检查一个reflect.Value
类型变量的"可写性":
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("设置可写性 v:", v.CanSet())
}
分析:
调用了reflect.ValueOf
实现的一个CanSet()
函数,
什么是可写性
反射类型变量的一种属性--->赋予该变量修改底层存储数据的能力
可写性的决定因素:
由一个反射对象是否存储了原始值而决定
可写性的设计原理:
/*
示例代码
*/
var x float64 = 3.14
v:= reflect.ValueOf(x)
/*
上诉这段代码传递给reflect.ValueOf()函数的是值传递并非引用传递.这是不合法的
所以,"可写性"就是为了避免这个问题而设计的
*/
反射的工作机制:
如果反射修改变量x
就需要把修改的变量的指针传递给反射库,如--->初始化变量x
,然后创建一个指向它的反射对象:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // 获取到变量x的地址
fmt.Println("p的类型:", p.Type())
fmt.Println("可以设置p:", p.CanSet())
}
分析:
反射对象p
不可写,需要修改的是*p
获取p
指向的数据:
-
Value
类型的Elem
方法--->作用:能够对指针进行“解引用”,然后将结果存储到反射Value
类型对象v
示例代码:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x) // x的地址
v := p.Elem()
fmt.Println("设置变量v:", v.CanSet())
}
/*
此时的变量v代表了x
*/
总结:
-
在
go
语言当中不能够对指针进行运算符或者是修改操作.所以在这里需要将地址值转化成对应的变量值本身然后再进行修改 -
反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址
结构体
使用反射修改结构体的字段,有结构体的指针就可以修改它的字段
特点:
从结构体的类型中提取了字段的名字,每个字段本身是正常的reflect.Value
对象
package main
import (
"fmt"
"reflect"
)
/*
结构体反射,用结构体的地址创建反射变量,再修改它.
*/
func main() {
type T struct {
A int
B string
}
// 实例化结构体
t := T{1, "内侯啊"}
// 通过ValueOf函数获取指针然后调用Elem函数解指针
s := reflect.ValueOf(&t).Elem()
// 获取T的类型
typeOfT := s.Type()
// 像处理表格一样处理结构体
for i := 0; i < s.NumField(); i++ {
// 获取每一个格子
f := s.Field(i)
// 打印结果
fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface())
}
}
分析:
其中s
包含了一个可设置的反射对象,可以修改结构体字段--->按照数据表的方式修改字段(通过索引然后调用Set
函数修改):
package main
import (
"fmt"
"reflect"
)
/*
获取到结构体对象
修改结构体对象当中的字段
*/
func main() {
// 声明结构体
type T struct {
A int
B string
}
// 实例化结构体
t := T{2, "哦嗯侯啊"}
// 先打印一遍t
fmt.Println("刚开始初始化以后的t是:", t)
// 调用reflect.ValueOf函数获取反射的结构体对象指针,然后再调用Elem()函数获取到结构体对象本身
s := reflect.ValueOf(&t).Elem()
// 通过索引的方式设置结构体的字段信息--->这个可以结合到ORM模型去联想它这里的解决办法
s.Field(0).SetInt(3)
s.Field(1).SetString("这就被修改了?")
// 打印结构体信息
fmt.Println("修改后的t是:", t)
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律