Go语言基础之21--反射
一、变量介绍
1.1 变量的内在机制
A. 类型信息,这部分是元信息,是预先定义好的;比如:string、int等
B. 值类型,这部分是程序运行过程中,动态改变的;比如:是变量实例所存储的真是的值。
例1:

例2:

二、反射介绍
2.1 反射与空接口
A. 空接口可以存储任何类型的变量
B. 那么给你一个空接口,如何知道里面存储的是什么东西?此时我们就需要借助反射。
C. 在运行时动态的获取一个变量的类型信息和值信息,就叫反射。
2.2 如何分析?
针对一个空接口,如何知道里面存储的是什么东西?此时我们就需要借助反射。
如何分析呢?
A. 内置包 reflect
B. 获取类型信息: reflect.TypeOf
C. 获取值信息: reflect.ValueOf
以TestInterface函数为例:
func TestInterface(a interface{}) {
}
其要传递的值类型是空接口,我们要借助反射做的是概括为以下3点:
1、获取a的类型;
2、动态改变a里面存储的值;
3、如果a里面存储的是一个结构体,那可以通过反射调用结构体里面的方法;
2.3 基本数据类型分析
实例:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) //reflect.TypeOf获取类型信息
}
执行结果:

2.4 Type.Kind(),获取变量的类型
实例:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
t := reflect.TypeOf(x)
fmt.Println("type:", t.Kind())
}
执行结果:

kind函数在go源码底层提供判断可选的类型如下:
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
2.5 reflect.ValueOf,获取变量的值相关信息
ValueOf获取的是变量实例的所有信息(包括类型、值等等)
实例:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
//和reflect.TypeOf功能是一样的
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
}
执行结果:

2.6 通过反射设置变量的值
实例:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
v.SetFloat(6.8)
fmt.Println("value:", v.Float())
}
执行结果:

我们此处修改变量的值,变量是值类型,修改是不生效的,存在这个问题,反射是不允许执行的,会直接panic的,那么我们就此进行改进,改进实例如下:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
//传地址进去,不传地址的话,改变的是副本的值
//所以在reflect包里直接崩溃了!!!!!
v := reflect.ValueOf(&x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
v.SetFloat(6.8)
fmt.Println("value:", v.Float())
}
执行结果如下:

可以发现虽然直接传入指针,但是依然有问题,因为我们传递的是地址(指针),而我们要改变的是这个地址对应的值,下面我们就这个问题在进行改进,改进实例如下:
package main import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
//传地址进去,不传地址的话,改变的是副本的值
//所以在reflect包里直接崩溃了!!!!!
v := reflect.ValueOf(&x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("kind is point:", v.Kind() == reflect.Ptr)
//通过Elem()获取指针指向的变量,从而完成赋值操作。
//正常操作是通过*号来解决的,比如
//var *p int = new(int)
//*p = 100
v.Elem().SetFloat(6.8)
fmt.Println("value:", x)
}
执行结果:

可以发现彻底解决了
2.7 补充:完整例子
实例如下:
package main import (
"fmt"
"reflect"
) func TestType(a interface{}) {
t := reflect.TypeOf(a) //获取a变量存的类型信息
fmt.Printf("t = %v\n", t) kind := t.Kind()
switch kind {
case reflect.Int:
fmt.Printf("a is int\n")
case reflect.String:
fmt.Printf("a is string\n")
}
} func TestValue(a interface{}) {
v := reflect.ValueOf(a) //ValueOf函数返回的是一个value结构体,通过value就可以操作a,现在都赋值给v,所以我们就可以通过v去间接操作a,v也就相当于拿到了a的所有信息
//注意v.Type() 和 reflect.TypeOf()的功能一样 //动态改变a的值
t := v.Type()
switch t.Kind() { //获取变量的类型
case reflect.Int:
v.SetInt()
case reflect.String:
v.SetString("xxxxxxx")
case reflect.Ptr:
t1 := v.Elem().Type() //v.Elem就相当于在变量前加个*号,获取该地址对应的值,此处就是获取指针变量对应的真实信息
switch t1.Kind() {
case reflect.Int:
v.Elem().SetInt()
fmt.Printf("ptr is int\n")
case reflect.String:
v.Elem().SetString("xxxxxxx")
fmt.Printf("ptr is string\n")
}
fmt.Printf("a is point type\n")
} } func main() {
var a int
TestType(a)
fmt.Printf("a=%v\n", a)
var b string
TestType(b) TestValue(&a)
TestValue(&b)
fmt.Printf("a=%v b=%v", a, b)
}
执行结果:

三、结构体反射
最常见的还是结构体反射,我们该如何通过反射去操作一个结构体(可以通过反射去获取结构体中的字段信息以及调用结构体里面的方法)
3.1 获取结构体类型相关信息
实例:
package main import (
"fmt"
"reflect"
) type S struct {
A int
B string
} func main() {
s := S{, "skidoo"}
v := reflect.ValueOf(s)
t := v.Type()
for i := ; i < v.NumField(); i++ {
f := v.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
t.Field(i).Name, f.Type(), f.Interface())
}
}
执行结果:

3.2 设置结构体相关字段的值
实例:
package main import (
"fmt"
"reflect"
) type S struct {
A int
B string
} func main() {
s := S{, "skidoo"}
v := reflect.ValueOf(&s)
t := v.Type()
v.Elem().Field().SetInt()
for i := ; i < v.Elem().NumField(); i++ {
f := v.Elem().Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
t.Elem().Field(i).Name, f.Type(), f.Interface())
}
}
执行结果:

3.3 获取结构体的方法信息
实例:
package main import (
"fmt"
"reflect"
) type S struct {
A int
B string
} func (s *S) Test() {
fmt.Println("this is a test")
}
func main() {
s := S{, "skidoo"}
v := reflect.ValueOf(&s)
t := v.Type() //获取类型信息
v.Elem().Field().SetInt() //将第0个字段设置成100
fmt.Println("method num:", v.NumMethod()) //NumMethod获取方法的数量
for i := ; i < v.NumMethod(); i++ {
f := t.Method(i) //将获取到的第i个方法的类型信息存到f
fmt.Printf("%d method, name:%v, type:%v\n", i, f.Name, f.Type)
}
}
3.4 调用结构体中的方法
实例1:
要调用结构体中的方法,就需要先用ValueOf获取实例的信息,类型只是元信息,
实例:
package main import (
"fmt"
"reflect"
) type S struct {
A int
B string
} func (s *S) Test() { //Test方法没有参数
fmt.Println("this is a test")
}
func (s *S) SetA(a int) {
s.A = a
}
func main() {
s := S{, "skidoo"}
v := reflect.ValueOf(&s)
m := v.MethodByName("Test") //m就是Test方法的实例,通过MethodByname方法来获取,使用该前提是你要知道结构体的方法是什么
var args1 []reflect.Value //有参数是通过切片传进去,没有参数,就是一个空切片
m.Call(args1) //借助call方法进行调用
setA := v.MethodByName("SetA")
var args2 []reflect.Value
args2 = append(args2, reflect.ValueOf()) //往切片中追加一个int 100
setA.Call(args2)
fmt.Printf("s:%#v\n", s)
}
执行结果:

实例2:
package main import (
"fmt"
"reflect"
) type User struct {
Name string `json:"name"`
Age int
Sex string
} func (u *User) Print() {
fmt.Printf("name:%s age:%d sex:%s\n", u.Name, u.Age, u.Sex)
} func (u *User) SetName(name string) {
u.Name = name
} func TestValue(a interface{}) { //调用没有参数的方法
v := reflect.ValueOf(a)
methodNum := v.NumMethod()
fmt.Printf("method:%v\n", methodNum) m := v.MethodByName("Print") var args []reflect.Value
m.Call(args) //调用有参数的方法
v = reflect.ValueOf(a)
m = v.MethodByName("SetName") args = args[:]
args = append(args, reflect.ValueOf("hello world"))
m.Call(args)
} func main() { var user User
user.Name = "xxx"
user.Age =
user.Sex = "man"
TestValue(&user)
fmt.Printf("user:%#v\n", user) }
执行结果:

3.5 获取结构体中tag信息
实例:
package main import (
"fmt"
"reflect"
) type S struct {
F string `species:"gopher" color:"blue" json:"f"`
} func main() {
s := S{}
st := reflect.TypeOf(s)
field := st.Field()
fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"),
field.Tag.Get("json"))
}
执行结果:

3.6 小例子
package main import (
"fmt"
"reflect"
) type User struct {
Name string `json:"name"`
Age int
Sex string
} //1. 获取a的类型
//2. 我要动态改变a里面存的值
//3. 如果a里面存储的是一个结构体,那可以通过反射获取结构体中的字段信息以及调用结构体里面的方法
func TestValue(a interface{}) { v := reflect.ValueOf(a)
t := v.Type()
switch t.Kind() { //获取变量的类型
case reflect.Struct: //假定变量是Struct结构体
fieldNum := t.NumField() //通过NumField拿到结构体中的字段数量
fmt.Printf("field num:%d\n", fieldNum)
for i := ; i < fieldNum; i++ {
field := t.Field(i) //字段的类型信息
vField := v.Field(i) //变量的实例的值的相关信息 fmt.Printf("field[%d] name:%s, json key:%s, val:%v\n",
i, field.Name, field.Tag.Get("json"), vField.Interface()) //因为这里不确定值的类型,所以通过Interface()自动帮我们判别
}
}
} func main() { var user User
user.Name = "harden"
user.Age =
user.Sex = "man"
TestValue(user)
fmt.Printf("user:%#v\n", user)
}
执行结果:

四、案例:序列化
下面我们借助反射写了一个json序列化的包
目录结构如下:

实例如下:
json.go:
package reflect_json import (
"fmt"
"reflect"
) func Marshal(data interface{}) (jsonStr string) { //data就是用户传进来的数据信息,返回的是序列化完的json序列串 t := reflect.TypeOf(data) //获取类型信息
v := reflect.ValueOf(data)
switch t.Kind() { //猜是什么类型
case reflect.String, reflect.Int, reflect.Int32: //简单数据类型处理几乎是一致的
jsonStr = fmt.Sprintf("\"%v\"", data) // \"表示双引号
case reflect.Struct: //结构体
numField := t.NumField() //获取结构体字段数量
for i := ; i < numField; i++ {
//类型信息
name := t.Field(i).Name //获取字段名字
tag := t.Field(i).Tag.Get("json") //获取有tag的字段名(此处针对json)
if len(tag) > { //有tag,优先使用tag
name = tag
}
//值信息
vField := v.Field(i) //返回值是一个Value的结构体
vFieldValue := vField.Interface() //想要获取字段的值,用interface即可
//拼接json
if t.Field(i).Type.Kind() == reflect.String {
jsonStr += fmt.Sprintf("\"%s\":\"%v\"", name, vFieldValue) //如果是字符串就加双引号
} else {
jsonStr += fmt.Sprintf("\"%s\":%v", name, vFieldValue) //不是字符串值不用加双引号
} //json串的话,字段与字段之间还有1个单引号,最后一个字段没有逗号
if i != numField- { //如果不是最后一个,就加一个逗号
jsonStr += ","
}
} jsonStr = "{" + jsonStr + "}" //最后在最前面和最后面加一个大括号
}
return
}
接下来我们通过一个小例子验证一下我们开发的这个包是否好用
实例如下:
main.go:
package main import (
"fmt" "9/after_class/reflect_json"
) /*
{
"name": "xxx",
"Age":100,
"Sex": "xx"
}
*/
type User struct {
Name string `json:"name"`
Age int
Sex string
} func main() {
var a string = "hello world"
jsonStr := reflect_json.Marshal(a)
fmt.Printf(jsonStr) var user User
user.Age =
user.Name = "user01"
user.Sex = "man" jsonStr = reflect_json.Marshal(user)
fmt.Printf("user marshal:%s\n", jsonStr)
}
执行结果如下:

五、反射总结以及应用场景
5.1 反射总结
A. 在运行时动态的获取一个变量的类型信息和值信息
5.2 应用场景
A. 序列化和反序列化,比如json, protobuf等各种数据协议
B. 各种数据库的ORM, 比如gorm, sqlx等数据库中间件
Go语言基础之21--反射的更多相关文章
- c语言基础:数据类型 分类: iOS学习 c语言基础 2015-06-10 21:43 9人阅读 评论(0) 收藏
C语言基本数据类型大体上分为: 整型 和 浮点型 字节: 计算机中最小的储存单位 1 Byte = 8 bit 整型: int 4 ...
- GO_09:GO语言基础之reflect反射
反射reflection 1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地 2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息 3. 反 ...
- GO语言基础之reflect反射
反射reflection 1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地 2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息 3. 反 ...
- C语言基础:进制转换,变量,常量,表达式,基本数据类型,输出函数,输入函数,运算符. 分类: iOS学习 c语言基础 2015-06-10 21:39 25人阅读 评论(0) 收藏
二进制:以0b开头,只有0和1两种数字.如0101 十进制:0~9十个数字表示.如25 十六进制:以0~9,A~F表示,以0X开头.如0X2B 十进制转换为X进制:连除倒取余 X进制转换为十进制:按权 ...
- C语言基础:数组 分类: iOS学习 c语言基础 2015-06-10 21:40 7人阅读 评论(0) 收藏
数组:是由一组具有相同数据类型的数据组合而来. 数组定义:元素类型修饰符 数组名[数组个数]={元素1,元素2....}; int arr[ 2 ]={1,2}; //正确 int arr[ ...
- C语言基础:内存 分类: iOS学习 c语言基础 2015-06-10 21:59 23人阅读 评论(0) 收藏
全局变量:定义在函数之外.(不安全) 局部变量;定义在函数之内. 内存的划分:1栈区 2堆区 3静态区(全局区) 4常量区 5代码区 栈区..静态区.常量区.代码区的数据都是由系统分配和释放 ...
- C语言基础:函数指针 分类: iOS学习 c语言基础 2015-06-10 21:55 15人阅读 评论(0) 收藏
函数指针:指向函数的指针变量. 函数名相当于首地址. 函数指针定义:返回值类型 (*函数指针变量名)(参数类型1,参数类型2,....)=初始值 函数指针类型:返回值类型 (*)(参数类型1,参数 ...
- C语言基础:指针初级(补充) 分类: iOS学习 c语言基础 2015-06-10 21:54 19人阅读 评论(0) 收藏
结构体指针:指向结构体指针的变量的指针. 结构体指针指向结构体第一个成员变量的首地址 ->: 指向操作符 定义的指针变量必须指向结构体的首地址,才可以使用 -> 访问结构体成员变量 ...
- C语言基础:初级指针 分类: iOS学习 c语言基础 2015-06-10 21:50 30人阅读 评论(0) 收藏
指针:就是地址. & 取地址运算符 %p 打印地址占位符 int a=0; printf("%p ",&a); 指针变量:用来存放地址的变量 定义: ...
- C语言基础:函数(Function) 分类: iOS学习 c语言基础 2015-06-10 21:48 14人阅读 评论(0) 收藏
函数:一段具有某些特定功能的代码段. 使用函数的严格规定: 1.函数声明 2.函数定义 3.函数调用 函数声明:告知系统编译器该系统的函数名,函数参数,参数类型,参数个数,参数顺序等等,以便函数调用时 ...
随机推荐
- sqlplus 连接数据库执行SP
.bashrc export HOME= export LANG="C" .bash_profile #明码 #export LOG_USER=lhcx #export LOG_P ...
- ListView里面嵌套CheckBox
布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro ...
- 【总结整理】地理信息系统GIS的基础坐标知识
关于辰青2018.03.31 1.地理坐标系和投影坐标系的区别 答案: 1.地理坐标系统是一种球面坐标,而投影坐标系统是平面坐标 2.投影坐标系统在二维平面上有恒定的长度.角度和面积 3.投影坐标系统 ...
- POJ 1220 高精度/进制转换
n进制转m进制,虽然知道短除法但是还是不太理解,看了代码理解一些了: 记住这个就好了: for(int k=0;l; ){ for(int i=l ; i>=1 ; i--){ num[i - ...
- Luogu 4197 Peaks
BZOJ 3545 带权限. 考虑离线,把所有边按照从小到大的顺序排序,把所有询问也按照从小到大的顺序排序,然后维护一个并查集和一个权值线段树,每处理一个询问就把比这个询问的$x$更小的边连上,具体来 ...
- Entity Framework Tutorial Basics(24):Update Single Entity
Update Existing Entity using DBContext in Disconnected Scenario: In this chapter, you will learn how ...
- a标签空的情况下 IE6 IE7下点击无效
如果给空a标签定义了宽度和高度且使用了absolute,则在IE6和IE7中点击无效. 两种解决方法(主要是针对a标签不能设置背景情况): 1.给a标签添加样式:background: ...
- AR# 30522:LogiCORE RapidIO - How do system_reset and link_reset work?
Description How do system_reset and link_rest work? Solution lnk_linkreset_n (input): In Xilinx SRIO ...
- nstallShield制作打包程序详解(图)
InstallShield产品,是安装工具领域事实上的标准.InstallShield 软件是软件安装.配置软件包和升级解决方案领域内公认的标准.InstallShield已经成为安全安装软件的标准解 ...
- STL 堆的使用
本来是要写leetcode上的merge k sorted lists那道题目,这个题目我还是很熟悉的,毕竟是看过算法导论的人,但是写的过程中对堆的维护代码还是挺多的,所以我想到了STL中的堆.下面就 ...