golang中的反射reflect整理

Reflect 整理

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

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

go 复制代码
a := 120
b := reflect.TypeOf(a)
c := reflect.ValueOf(a)
fmt.Println(b, c)

输出自然很简单,是int和120,这样看也看不出来和接口有什么关系,我们点开源码:

go 复制代码
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是相同的类型吗?

go 复制代码
type Myint int
var i int 
var j Myint

答:不是的,二者拥有不同的静态类型,尽管二者的底层类型都是 int,但在没有类型转换的情况下是不可以相互赋值的。 Go提供了布尔、数值和字符串类型的基础类型,还有一些使用这些基础类型组成的复合类型,比如数组、结构体、指针、切片、map 和channel 等。interface也可以称为一种复合类型

接下来看看reflect包中的一些常用函数:

看以下例子:

go 复制代码
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()函数则是还原接口值

值修改

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

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

我们运行时会发现错误:

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

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

go 复制代码
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)
}

输出:

反射结构

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

go 复制代码
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) // <main.NotknownType Value>
	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:

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

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

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

go 复制代码
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() 在没有格式字符串的情况下还能如此漂亮地输出。

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

相关推荐
Grassto1 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto3 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室4 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题4 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉6 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想
asaotomo6 天前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
码界奇点7 天前
基于Gin与GORM的若依后台管理系统设计与实现
论文阅读·go·毕业设计·gin·源代码管理
迷迭香与樱花7 天前
Gin 框架
go·gin
只是懒得想了7 天前
用Go通道实现并发安全队列:从基础到最佳实践
开发语言·数据库·golang·go·并发安全
fenglllle8 天前
使用fyne做一个桌面ipv4网段计算程序
开发语言·go