欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > Go —— 反射

Go —— 反射

2025/5/2 5:46:33 来源:https://blog.csdn.net/Wksycxy/article/details/141360752  浏览:    关键词:Go —— 反射

反射

反射是什么?

  • 反射是运行时检查自身结构的机制
  • 反射是困惑的源泉。

反射特性与 interface 紧密相关。

接口

1. 类型

Go是静态类型语言,比如int、float32、[]byte,等等。每个变量都有一个静态类型,而且在编译时就确定了。

考虑如下几类变量声明:

type Myint intvar i int
var j Myint

变量 i 和 j 是相同的类型吗?不是的,二者拥有不同的静态类型,尽管二者的底层类型都是 int ,但在没有类型转换的情况下是不可以相互赋值的。

Go提供了布尔、数值和字符串类型的基础类型,还有一些使用这些基本类型组合的复合类型,比如数组、结构体、指针、切片、map和 channel 等。interface 也可以称为一种复合类型。

2. interface 类型

每个 interface 类型代表一个特性的方法集,方法集中的方法称为接口。比如:

type Animal interface {Speak() string
}

Animal 就是一个接口类型,其包含一个 Speak() 方法

1)interface变量

就像任何其他类型一样,我们也可以声明 interface 类型的变量。比如:

var animal Animal

上面的 animal 变量的值为 nil,它没有赋值,它可以存储什么值呢?

2)实现接口

任何类型只要实现了 interface 类型的所有方法,就可以声称该类型实现了这个接口,该类型的变量就可以存储到 interface 变量中。

比如结构体 Cat 实现了Speak() 方法:

type Cat struct {
}func (c Cat) Speak() string {return "Meow"
}

结构体Cat的变量就可以存储到 animal 变量中:

var animal Animal
var cat Cat
animal = cat

interface 变量可以存储到任意实现了该接口类型的变量。

3)复合类型

为什么 interface 变量可以存储任意实现了该类型接口的变量。

因为 interface 类型的变量在存储某个变量时会同时保存变量类型和变量值。

type iface struct {tab *itab // 保存变量类型data unsafe.Pointer // 变量值位于堆栈的指针
}

Go的反射就是在运行时操作 interface 中的值和类型的特性。

反射定理

interface 类型有一个(value、type)对,而反射就是操纵 interface 的这个(value,type)对的机制。具体一点说就是Go提供一组方式来获取 interface 的value 和 type。

1. reflect包

reflect包中提供了reflect.Typereflect.Value 两个类型,分别代表 interface 中的类型和value。

reflect包中同时提供两个方法来获取 interface 的 value 类型:

func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type

具体关系如下图所示:

在这里插入图片描述

在下面的描述中,我们称reflect.Type 和reflect.Value 为 interface 的反射对象

2. 反射定律

1)第一定律:反射可以将 interface 类型变量转换成反射对象

package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4t := reflect.TypeOf(x)fmt.Println("type:", t)v := reflect.ValueOf(x)fmt.Println("value:", v)
}

程序输出如下:

type: float64
value: 3.4

在上面的例子中,好像没有 interface 变量,实则不然,变量 x 在传入 reflect.TypeOf()函数时,实际上做了一次转化,x变量被转换成一个空接口传入,reflect.ValueOf(x)也是如此。该例子展示了反射的一个能力,即可以获取 interface 变量的类型和值,这是反射进一步操纵 interface变量的基础。

2)第二定律:反射可以将反射对象还原成 interface 对象

之所以叫“反射”,是因为反射对象与 interface 对象时可以相互转换的。

func foo() {var A interface{}A = 100v := reflect.ValueOf(A)B := v.Interface()if A == B {fmt.Println("A==B")} else {fmt.Println("A!=B")}
}

在上面的函数中,通过 reflect.ValueOf()获取接口变量A的反射对象,然后又通过反射对象的interface()获取B,结果A和B是相同的。

3)第三定律:反射对象可修改,value值必须是可设置的

通过反射可以将 interface 类型的变量转换成反射对象,可以使用该反射对象设置 interface 变量持有的值。

我们可以通过 reflect.Value 的一系列 SetXXX() 方法来设置反射对象的值。先看一个失败的例子:

package mainimport "reflect"func main() {var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) 
}

在上面的代码中,通过反射对象v设置新值,会触发panic。报错如下:

panic: reflect: reflect.Value.SetFloat using unaddressable value

错误原因是v是不可修改的,为什么会如此呢?

反射对象是否可修改取决于其所存储的值。上例中传入 reflect.ValueOf()函数的其实是x的值,而非x本身,即通过v修改其值是无法影响x的,也就是无效的修改,所以会报错。

如果构建v时使用x的地址就可实现修改了,但此时v代表的是指针地址,我们要设置的是指针所指向的内容,即我们想要修改*v。那怎么通过v修改x的值呢?

reflect.Value() 提供了Elem() 方法,可以获得指向 value 的指针。修正后的代码如下:

package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4v := reflect.ValueOf(&x)v.Elem().SetFloat(7.1)fmt.Println("x:"x)
}

输出如下:

x: 7.1

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词