3.24 Go之反射规则

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)
}

分析:

  1. 首先这个赋值是不允许的,编译器报错了.因为编译器认为变量bMyInt类型,虽然MyInt类型的基本类型是Int

  2. 要想成功的赋值,只能强制给变量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)
}

反射的关键:接口

接口的特点:

  1. 一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身)

  2. Go语言的特点可以举例说明:

    1. 一个类型声明实现了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类型

  1. 空接口--->表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。

Go反射定律

可以将“接口类型变量”转换为“反射类型对象”

reflect包下的两种类型:

  • Type

  • Value

对应的函数:

  • reflect.TypeOf

  • reflect.ValueOf

reflect.TypeOf的函数签名里包含一个空接口

调用reflect.TypeOf(x)时,x被存储在一个空接口变量中被传递过去,然后reflect.TypeOf对空接口变量进行拆解,恢复其类型信息

其他的一些函数:

  • reflect.Value有一个方法Type(),返回一个reflect.Type类型的对象。

  • TypeValue有一个Kind方法,返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice等。

反射库中提供的修改数据的方法:

  • SetInt

  • SetFloat

涉及概念:

  • 可修改性(settability)

可修改性的特点:

Valuegettersetter方法为了保证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.Printlnfmt.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
*/

总结:

  1. go语言当中不能够对指针进行运算符或者是修改操作.所以在这里需要将地址值转化成对应的变量值本身然后再进行修改

  2. 反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址

结构体

使用反射修改结构体的字段,有结构体的指针就可以修改它的字段

特点:

从结构体的类型中提取了字段的名字,每个字段本身是正常的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)
}

 

posted @   俊king  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示