当前位置:首页 > 编程笔记 > 正文
已解决

【golang】Reflect反射整理、值修改、反射结构体、应用

来自网友在路上 173873提问 提问时间:2023-11-04 14:08:27阅读次数: 73

最佳答案 问答题库738位专家为你答疑解惑

Reflect 整理

反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如:它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

反射通常用于检查一个变量的值或者类型,而这里面就和接口有很大关系,我们先举一个例子:

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 intfunc main() {var a intvar b MyInta = 120b = 240aK := 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())
}

最后输出:

Untitled

我们可以看到Kind()函数返回的是变量的底层类型,Interface()函数则是还原接口值

值修改

当我们想给元素值进行修改时,我们不能直接按照下面的方式修改:

func main() {var b MyInt = 240v := reflect.ValueOf(b)v.SetInt(120)fmt.Println(v)
}

我们运行时会发现错误:

Untitled

原因就在于v是不可设置的,我们可以看看源码:

Untitled

在函数中,我们传进去的其实是x的副本,而并非真实值,所以错误,所以应该是取出地址,获取地址对应的元素,进行修改,也就是:

type MyInt intfunc main() {var b MyInt = 240a := reflect.ValueOf(&b).Elem()fmt.Println(a.CanSet()) //是否可以修改值,true为可修改a.SetInt(120)fmt.Println(a)
}

输出:

Untitled

反射结构

有时候反射也可以是一个结构体,那么就又有一些对应函数,先从例子入手:

package mainimport ("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) // <main.NotknownType Value>typ := reflect.TypeOf(secret)    // 输出:main.NotknownType// 替代项:// typ := value.Type()  // main.NotknownTypefmt.Println(typ)knd := value.Kind() // 输出底层类型:structfmt.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]
}

输出:

Untitled

当在上面的代码中修改值时,会panic:

//error: panic: reflect.Value.SetString using value obtained using unexported field
value.Field(i).SetString("C#")

这是因为结构中只有被导出字段(首字母大写)才是可设置的

所以我们也是跟上面差不多的操作,取地址,然后进行更改,具体示例:

package mainimport ("fmt""reflect"
)type T struct {A intB 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)
}

输出:

Untitled

标准库中应用

比如我们经常使用的控制台输出函数:

Untitled

Println() 使用反射包来解析这个参数列表。所以,Println() 能够知道它每个参数的类型。因此格式化字符串中只有 %d 而没有 %u%ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print()Println() 在没有格式字符串的情况下还能如此漂亮地输出。

今天的小结就到这里,给自己放个假,出去放松一下

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"【golang】Reflect反射整理、值修改、反射结构体、应用":http://eshow365.cn/6-31858-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!