反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如:它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。
反射通常用于检查一个变量的值或者类型,而这里面就和接口有很大关系,我们先举一个例子:
a := 120
b := reflect.TypeOf(a)
c := reflect.ValueOf(a)
fmt.Println(b, c)
输出自然很简单,是int和120,这样看也看不出来和接口有什么关系,我们点开源码:
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
// Noescape so this doesn't make i to escape. See the comment
// at Value.typ for why this is safe.
return toType((*abi.Type)(noescape(unsafe.Pointer(eface.typ))))
}
其中**emptyInterface
是Go语言中空接口(interface{}
**)的内部表示方式,也就是说它先将参数转换为空接口类型,之后再输出类型
之后我查阅了点资料,补充了以下知识:
eface.typ
是**emptyInterface
**结构中的字段,它存储了实际类型的信息。unsafe.Pointer(eface.typ)
将**eface.typ
**的地址转换为不安全的指针。noescape
函数用于防止接口值i
**逃逸(在Go中,逃逸指的是将局部变量分配到堆上),这是一种编译器优化。在这里,它确保不会出现逃逸。我们知道 Go 是静态类型语言,比如 int、float32、[]byte,等等。每个变量都有一个静态类型,而且在编译时就确定了。
接下来来个题目:请问,变量 i和 j是相同的类型吗?
type Myint int
var i int
var j Myint
答:不是的,二者拥有不同的静态类型,尽管二者的底层类型都是 int,但在没有类型转换的情况下是不可以相互赋值的。
Go提供了布尔、数值和字符串类型的基础类型,还有一些使用这些基础类型组成的复合类型,比如数组、结构体、指针、切片、map 和channel 等。interface也可以称为一种复合类型
接下来看看reflect包中的一些常用函数:
看以下例子:
type MyInt int
func main() {
var a int
var b MyInt
a = 120
b = 240
aK := reflect.ValueOf(a)
bK := reflect.ValueOf(b)
fmt.Println(reflect.TypeOf(a), aK.Kind(), aK.Type(), aK.Interface())
fmt.Println(reflect.TypeOf(b), bK.Kind(), bK.Type(), bK.Interface())
}
最后输出:
我们可以看到Kind()函数返回的是变量的底层类型,Interface()函数则是还原接口值
当我们想给元素值进行修改时,我们不能直接按照下面的方式修改:
func main() {
var b MyInt = 240
v := reflect.ValueOf(b)
v.SetInt(120)
fmt.Println(v)
}
我们运行时会发现错误:
原因就在于v是不可设置的,我们可以看看源码:
在函数中,我们传进去的其实是x的副本,而并非真实值,所以错误,所以应该是取出地址,获取地址对应的元素,进行修改,也就是:
type MyInt int
func main() {
var b MyInt = 240
a := reflect.ValueOf(&b).Elem()
fmt.Println(a.CanSet()) //是否可以修改值,true为可修改
a.SetInt(120)
fmt.Println(a)
}
输出:
有时候反射也可以是一个结构体,那么就又有一些对应函数,先从例子入手:
package main
import (
"fmt"
"reflect"
)
type NotknownType struct {
s1, s2, s3 string
}
func (n NotknownType) String() string {
return n.s1 + " - " + n.s2 + " - " + n.s3
}
// 设置变量
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
func main() {
value := reflect.ValueOf(secret) //
typ := reflect.TypeOf(secret) // 输出:main.NotknownType
// 替代项:
// typ := value.Type() // main.NotknownType
fmt.Println(typ)
knd := value.Kind() // 输出底层类型:struct
fmt.Println(knd)
//NumField()输出字段数量
for i := 0; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i)) //输出字段值
}
// 调用第一个签名在MotKnownType上的方法:
results := value.Method(0).Call(nil)
fmt.Println(results) // [Ada - Go - Oberon]
}
输出:
当在上面的代码中修改值时,会panic:
//error: panic: reflect.Value.SetString using value obtained using unexported field
value.Field(i).SetString("C#")
这是因为结构中只有被导出字段(首字母大写)才是可设置的
所以我们也是跟上面差不多的操作,取地址,然后进行更改,具体示例:
package main
import (
"fmt"
"reflect"
)
type T struct {
A int
B string
}
func main() {
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
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.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
}
输出:
比如我们经常使用的控制台输出函数:
Println()
使用反射包来解析这个参数列表。所以,Println()
能够知道它每个参数的类型。因此格式化字符串中只有 %d
而没有 %u
和 %ld
,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print()
和 Println()
在没有格式字符串的情况下还能如此漂亮地输出。
今天的小结就到这里,给自己放个假,出去放松一下