[TOC]
# 变量的内在机制
1. 类型信息,这部分是元信息,是预先定义好的
2. 值类型,这部分是程序运行过程中,动态改变的
反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
多插一句,Golang的gRPC也是通过反射实现的。
# 反射与空接口
空接口可以存储任何类型的变量
那么给你一个空接口,怎么判断里面存储的是什么东西?
在运行时动态的获取一个变量的类型和值信息就叫反射
内置包: reflect
获取类型信息: reflect.TypeOf
获取值信息: reflect.ValueOf
# 反射种类(kind)定义
Go程序中的类型(Type)指的是系统原生数据类型,如int、string、bool、float32等类型,以及使用type关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用typeAstruct{}定义结构体时,A就是struct{}的类型。种类(Kind)指的是对象归属的品种,在reflect包中有如下定义
~~~
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
~~~
Map、Slice、Chan属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于Ptr。type A struct{}定义的结构体属于Struct种类,\*A属于Ptr
# TypeOf和ValueOf
~~~
func reflect_example(a interface{}) {
t := reflect.TypeOf(a)
fmt.Printf("type of a is %v\n", t)
v := reflect.ValueOf(a)
fmt.Printf("value of a is %v\n", v)
k := t.Kind()
fmt.Println(k)
}
func main() {
var x int64 = 3
reflect_example(x)
}
~~~
# 运行时修改值
~~~
func reflect_example(a interface{}) {
v := reflect.ValueOf(a)
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("a is int64, store value is: %d\n", v.Int())
case reflect.Float64:
fmt.Printf("a is Float64, store value is: %f\n", v.Float())
case reflect.Ptr:
//指针类型 .Elem()相当于 指针取值
v.Elem().SetFloat(22.5)
fmt.Println("指针")
default:
fmt.Println("default")
}
}
func main() {
var x float64 = 3.4
reflect_example(&x)
fmt.Println(x)
}
~~~
~~~
var x float64 = 3.4
//这边要传地址,不然反射的是副本,下面修改副本的值会报错
v1 := reflect.ValueOf(&x)
//这边已经是指针,要用Elem,通过Elem()获取指针指向的变量,从而完成赋值操作
v1.Elem().SetFloat(4.3)
fmt.Println(v1.Elem().Float())
~~~
# 结构体属性
~~~
type Student struct {
Name string
Sex int
Age int
//abc string
}
func main() {
var s Student
v := reflect.ValueOf(s)
t := v.Type()
kind := t.Kind()
fmt.Println(kind) //struct
//查看字段数量,包含私有字段
fmt.Println(v.NumField())
//注意,私有获取不到,会报错的
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fmt.Printf("名字: %s 类型: %v 值: %v\n", t.Field(i).Name, field.Type(), field.Interface())
}
//修改结构体内部的值
v1 := reflect.ValueOf(&s)
//用索引方式
v1.Elem().Field(0).SetString("abc")
//指定名称方式
v1.Elem().FieldByName("Sex").SetInt(2)
v1.Elem().FieldByName("Age").SetInt(12)
fmt.Println(s)
}
~~~
输出
~~~
struct
3
名字: Name 类型: string 值:
名字: Sex 类型: int 值: 0
名字: Age 类型: int 值: 0
{abc 2 12}
~~~
# 结构体方法
~~~
func (s *Student) Test() {
fmt.Println("this is test")
}
func main() {
s := Student{23, "skidoo"}
v := reflect.ValueOf(&s)
t := v.Type()
v.Elem().Field(0).SetInt(100)
fmt.Println("method num: ", v.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
f := t.Method(i)
fmt.Printf("%d method, name: %v, type: %v\n", i, f.Name, f.Type)
}
}
~~~
输出
~~~
method num: 1
0 method, name: Test, type: func(*main.Student)
~~~
# 调用结构体方法
~~~
type Student struct {
A int
B string
}
func (s *Student) Test() {
fmt.Println("this is test")
}
func (s *Student) SetA (a int) {
s.A = a
}
func main() {
s := Student{23, "skidoo"}
//要引用传递,不然修改的是副本会报错
v := reflect.ValueOf(&s)
m := v.MethodByName("Test")
var args1 []reflect.Value
m.Call(args1)
setA := v.MethodByName("SetA")
var args2 []reflect.Value
//参数
args2 = append(args2, reflect.ValueOf(100))
setA.Call(args2)
fmt.Printf("s: %#v\n", s)
}
~~~
输出
~~~
this is test
s: main.Student{A:100, B:"skidoo"}
~~~
# 获取结构体中的tag信息
~~~
type Student struct {
F string `species:"gopher" color:"blue" json:"f"`
}
func main() {
s := Student{}
//要引用传递,不然修改的是副本会报错
v := reflect.TypeOf(s)
field := v.Field(0)
fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"), field.Tag.Get("json"))
~~~
输出
~~~
blue gopher f
~~~
- 基础
- 简介
- 主要特征
- 变量和常量
- 编码转换
- 数组
- byte与rune
- big
- sort接口
- 和mysql类型对应
- 函数
- 闭包
- 工作区
- 复合类型
- 指针
- 切片
- map
- 结构体
- sync.Map
- 随机数
- 面向对象
- 匿名组合
- 方法
- 接口
- 权限
- 类型查询
- 异常处理
- error
- panic
- recover
- 自定义错误
- 字符串处理
- 正则表达式
- json
- 文件操作
- os
- 文件读写
- 目录
- bufio
- ioutil
- gob
- 栈帧的内存布局
- shell
- 时间处理
- time详情
- time使用
- new和make的区别
- container
- list
- heap
- ring
- 测试
- 单元测试
- Mock依赖
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 详解
- plugin包
- 信号
- goto
- 协程
- 简介
- 创建
- 协程退出
- runtime
- channel
- select
- 死锁
- 互斥锁
- 读写锁
- 条件变量
- 嵌套
- 计算单个协程占用内存
- 执行规则
- 原子操作
- WaitGroup
- 定时器
- 对象池
- sync.once
- 网络编程
- 分层模型
- socket
- tcp
- udp
- 服务端
- 客户端
- 并发服务器
- Http
- 简介
- http服务器
- http客户端
- 爬虫
- 平滑重启
- context
- httptest
- 优雅中止
- web服务平滑重启
- beego
- 安装
- 路由器
- orm
- 单表增删改查
- 多级表
- orm使用
- 高级查询
- 关系查询
- SQL查询
- 元数据二次定义
- 控制器
- 参数解析
- 过滤器
- 数据输出
- 表单数据验证
- 错误处理
- 日志
- 模块
- cache
- task
- 调试模块
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密码
- 对称加密
- 非对称加密
- 单向散列函数
- 消息认证
- 数字签名
- mysql优化
- 常见错误
- go run的错误
- 新手常见错误
- 中级错误
- 高级错误
- 常用工具
- 协程-泄露
- go env
- gometalinter代码检查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高编译
- go get
- 代理
- 其他的知识
- go内存对齐
- 细节总结
- nginx路由匹配
- 一些博客
- redis为什么快
- cpu高速缓存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密码加密解密
- for 循环迭代变量
- 备注
- 垃圾回收
- 协程和纤程
- tar-gz
- 红包算法
- 解决golang.org/x 下载失败
- 逃逸分析
- docker
- 镜像
- 容器
- 数据卷
- 网络管理
- 网络模式
- dockerfile
- docker-composer
- 微服务
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell调用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb