Go小白入门10 - 反射机制

本文详细介绍了Go语言中的反射机制,包括反射的作用、如何实现反射及反射的三定律。反射允许程序在运行时检查和修改自身状态,常用于不定参数类型函数、对象关系映射等场景。文章通过实例演示了如何使用反射修改内容和调用方法,并提醒注意反射可能带来的性能影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 反射是什么?

反射:程序可以访问、检测和修改它本身状态或行为的一种能力
Go语言的反射机制:在运行时更新变量和检查它们的值、调用它们的方法;但在编译时并不知道这些变量的具体类型

2. 反射有什么作用?

1)编写不定传参类型函数或传入类型过多
对象关系映射

type User struct {
	gorm.Model
	Name         string
	Age          sql.NullInt64
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"` // set field size to 255
	MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
	Num          int     `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
	Address      string  `gorm:"index:addr"` // create index with name `addr` for address
	IgnoreMe     int     `gorm:"-"` // ignore this field
  }
  
  var users []User
  db.Find(&users)

2)不确定调用哪个函数,需要根据某些条件动态执行

参数funcPtr以接口的形式传入函数指针,参数args以可变参数的形式传入 => bridge函数中可用反射来动态执行funcPtr函数

func bridge(funcPtr interface{}, args ...interface{})

3. 如何实现反射?

Go的反射机制是通过接口来进行的

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数 => 可以在运行时检测类型的信息、改变类型的值。

3.1 反射三定律

1)反射可将"接口类型变量"转换为"反射类型对象"
package main
import (
	"fmt"
	"reflect"
)

func main() {
	num := 1
	v := reflect.ValueOf(num)
	t := reflect.TypeOf(num)

	fmt.Println("Reflect : Num.Value = ", v) //Reflect : Num.Value =  1
	fmt.Println("Reflect : Num.Type = ", t)  //Reflect : Num.Type =  int
}

解析:
reflect.ValueOf / reflect.TypeOf的函数签名如下,两个方法的参数类型都是空接口。

func TypeOf(i interface{}) Type

func (v Value) Interface() (i interface{})

调用reflect.TypeOf(x)时:x被存储在空接口中 => reflect.TypeOf对空接口进行拆解 => 将接口类型变量转换为反射类型变量

2)反射可将"反射类型对象"转换为"接口类型变量"

origin := v.Interface().(int)

根据一个reflect.Value类型的变量,可使用 Interface 方法恢复其接口类型的值

package main

import (
	"fmt"
	"reflect"
)

func main() {
	num := 1
	v := reflect.ValueOf(num)
	t := reflect.TypeOf(num)

	fmt.Println("Reflect : Num.Value = ", v) //Reflect : Num.Value =  1
	fmt.Println("Reflect : Num.Type = ", t)  //Reflect : Num.Type =  int

	origin := v.Interface().(int)
	fmt.Println(origin)
}
3)若要修改"反射类型对象",其值必须"可写"
package main
import "reflect"

func main() {
	num := 1
	v := reflect.ValueOf(num)
	v.SetInt(2)
}

上面的v.SetInt报错
panic: reflect: reflect.Value.SetInt using unaddressable value
goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x82)

反射对象v包含的是副本值,所以无法修改!!
可通过CanSet函数来判断反射对象是否可以修改

fmt.Println("v的可写性:", v.CanSet()) // false

3.2 反射小结

  • 反射对象包含了接口变量存储的值 & 类型
  • 可以通过反射对象修改原始值吗?
    • 反射对象中包含的值是原始值 => 【可以】
    • 反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址) => 【不可以】

4. 反射修改内容 & 调用方法

反射使代码执行效率较慢,因为

  • 涉及内存分配 & 后续垃圾回收
  • reflect实现里有大量枚举(for循环 e.g类型之类的)

p.Elem() - 获取当前指针指向的元素值 (取元素,等效于对指针类型变量做*操作)

package main

import (
	"fmt"
	"reflect"
)

func main() {
	num := 1
	fmt.Println(num) // 1

	// 1) 通过反射修改内容
	p := reflect.ValueOf(&num)
	v := p.Elem()
	v.SetInt(2)
	fmt.Println(num) // 2

	// 2) 通过反射调用方法
	function := hello
	functionV := reflect.ValueOf(function)
	functionV.Call(nil)
}

func hello() {
	fmt.Println("Hello world!")
}

5. 参考资料

Datawhale组队学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值