一、变量介绍

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--反射的更多相关文章

  1. c语言基础:数据类型 分类: iOS学习 c语言基础 2015-06-10 21:43 9人阅读 评论(0) 收藏

    C语言基本数据类型大体上分为: 整型 和 浮点型   字节: 计算机中最小的储存单位     1 Byte = 8 bit 整型:         int     4                  ...

  2. GO_09:GO语言基础之reflect反射

    反射reflection 1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地 2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息 3. 反 ...

  3. GO语言基础之reflect反射

    反射reflection 1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地 2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息 3. 反 ...

  4. 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进制转换为十进制:按权 ...

  5. C语言基础:数组 分类: iOS学习 c语言基础 2015-06-10 21:40 7人阅读 评论(0) 收藏

    数组:是由一组具有相同数据类型的数据组合而来. 数组定义:元素类型修饰符 数组名[数组个数]={元素1,元素2....};  int arr[ 2 ]={1,2};    //正确 int arr[ ...

  6. C语言基础:内存 分类: iOS学习 c语言基础 2015-06-10 21:59 23人阅读 评论(0) 收藏

    全局变量:定义在函数之外.(不安全)   局部变量;定义在函数之内. 内存的划分:1栈区   2堆区  3静态区(全局区) 4常量区 5代码区 栈区..静态区.常量区.代码区的数据都是由系统分配和释放 ...

  7. C语言基础:函数指针 分类: iOS学习 c语言基础 2015-06-10 21:55 15人阅读 评论(0) 收藏

    函数指针:指向函数的指针变量. 函数名相当于首地址. 函数指针定义:返回值类型  (*函数指针变量名)(参数类型1,参数类型2,....)=初始值 函数指针类型:返回值类型  (*)(参数类型1,参数 ...

  8. C语言基础:指针初级(补充) 分类: iOS学习 c语言基础 2015-06-10 21:54 19人阅读 评论(0) 收藏

    结构体指针:指向结构体指针的变量的指针. 结构体指针指向结构体第一个成员变量的首地址 ->:   指向操作符 定义的指针变量必须指向结构体的首地址,才可以使用  ->  访问结构体成员变量 ...

  9. C语言基础:初级指针 分类: iOS学习 c语言基础 2015-06-10 21:50 30人阅读 评论(0) 收藏

    指针:就是地址. &   取地址运算符 %p   打印地址占位符 int a=0; printf("%p ",&a);    指针变量:用来存放地址的变量 定义: ...

  10. C语言基础:函数(Function) 分类: iOS学习 c语言基础 2015-06-10 21:48 14人阅读 评论(0) 收藏

    函数:一段具有某些特定功能的代码段. 使用函数的严格规定: 1.函数声明 2.函数定义 3.函数调用 函数声明:告知系统编译器该系统的函数名,函数参数,参数类型,参数个数,参数顺序等等,以便函数调用时 ...

随机推荐

  1. import requests

  2. HDU 5242 树链剖分思想的贪心

    题意及博客 树链剖分分为2步,第一次求出深度,重儿子,第二次求出重链,用到了启发式的思想,即对于比较重的儿子,尽量去完整的维护它.类似于我们去合并两个堆,明显把小的堆逐个插入大的堆中会比大的往小的插更 ...

  3. JavaScript toLowerCase() 方法

    定义和用法 toLowerCase() 方法用于把字符串转换为小写. 语法 stringObject.toLowerCase() 返回值 一个新的字符串,在其中 stringObject 的所有大写字 ...

  4. Java 访问 Kylin 总结

    这次开发功能是OEM统计报表.统计报表的数据由大数据平台部的同事收集,数据的展示由我们部门开发. 大数据那边使用 Kylin 分布式分析引擎(kylin官方文档). Kylin 虽比较偏向大数据相关, ...

  5. ???Spring集成MyBatis02 【不推荐使用,了解即可】

    2017年5月19日09:31:22 由于该种方法比较麻烦,所以三少暂时不更新,哈哈哈:待更新...

  6. JVM的内存管理、对象的生命周期、内存泄漏

    1 JVM内存 分为“堆”.“栈”和“方法区”三个区域,分别用于存储不同的数据 1.1 堆 JVM在其内存空间开辟一个称为”堆”的存储空间,这部分空间用于存储使用new关键字所创建的对象. 1.2 栈 ...

  7. dpdk中uio技术

    总结一下dpdk的uio技术 一:什么是uio技术 UIO(Userspace I/O)是运行在用户空间的I/O技术,Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可, ...

  8. vray学习笔记(5)-学习资料

    首先肯定是vray的官方的资料了: 一个是教程 https://docs.chaosgroup.com/display/VRAY3MAX/Tutorials 一个是帮助文件 https://docs. ...

  9. HBase 协处理器统计行数

    环境:cdh5.1.0 启用协处理器方法1. 启用协处理器 Aggregation(Enable Coprocessor Aggregation) 我们有两个方法:1.启动全局aggregation, ...

  10. Luogu 3008 [USACO11JAN]道路和飞机Roads and Planes

    BZOJ2200 听说加上slf优化的spfa的卡过,真的不想写这些东西. 考虑使用堆优化的dij算法. 先加上所有双向边,然后dfs一下搜出所有由双向边构成的联通块,然后加上所有的单向边,一边对所有 ...