一、变量介绍

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. 关于FILL_PARENTE和match_parent布局属性

    在观看早期的代码的时候,经常会看到FILL_PARENT属性,但是新的代码中却有了MATCH_PARENT 那么,两者有何区别呢? 答案是,没有,只是换了个名字而已,均为-1

  2. ubuntu16配置Mask-RCNN

    一.安装Anaconda3 1.下载 下载地址:https://www.continuum.io/downloads 2.安装 在文件目录下执行:bash Anaconda3-4.2.0-Linux- ...

  3. 3DPDF是个什么东西?

    就是可以把3D模型放入到PDF中,然后客户可以直接用adobe reader查看这个PDF.经过搜索发现,大多数PDF编辑软件都没有直接把3D模型插入到PDF中的功能. 很多是3D软件自身提供的,比如 ...

  4. JavaPersistenceWithHibernate第二版笔记-第七章-001Mapping a set(@ElementCollection、@CollectionTable、@JoinColumn、)

    一.结构 二.代码 1. package org.jpwh.model.collections.setofstrings; import org.jpwh.model.Constants; impor ...

  5. ZROI2018普转提day2t2

    传送门 分析 我们发现2R+C实际就相当于R行C列的子集的个数.因此我们可以将所有集合的子集个数转换为每个集合属于的集合的个数.所以我们可以求出: 这个式子的意义为对于选i行j列的情况的所有方案乘上i ...

  6. thrift使用小记

        Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目.Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数 ...

  7. web.xml文件的Url-pattern 节点配置

  8. 【java】小技巧和注意事项

    1.字符串反向比较  “abc”.equals(sting) 2.文档注释 /** *注释内容 */ 3.

  9. Django之博客系统:用户注册和Profile

    前面章节介绍了用户的登录,退出.这一章将介绍用户的注册.首先需要创建一个表单来让用户填写用户名,密码等信息.创建UserRegistrationFrom表单.并指定model为User类 from d ...

  10. NSString 字符串

    0.字符串常用操作 自动补充方法:当字符串长度不够需要自动补充到一定的位数 OC字符串与C语言字符串之间的相互转换 1.不可变字符串的创建 // 直接创建不可变字符串 /* 在 OC 中,使用 @&q ...