Go语言学习之反射的用法详解

目录
  • 1. reflect 包
    • 1.1 获取变量类型
    • 1.2 断言处理类型转换
  • 2. ValueOf
    • 2.1 获取变量值
    • 2.2 类型转换
  • 3. Value.Set
    • 3.1 设置变量值
    • 3.2 示例
  • 4. 结构体反射
    • 4.1 查看结构体字段数量和方法数量
    • 4.2 获取结构体属性
    • 4.3 更改属性值
    • 4.4 Tag原信息处理
  • 5. 函数反射
  • 6. 方法反射
    • 6.1 使用 MethodByName 名称调用方法
    • 6.2 使用 method 索引调用方法

反射指的是运行时动态的获取变量的相关信息

1. reflect 包

类型是变量,类别是常量

reflect.TypeOf,获取变量的类型,返回reflect.Type类型

reflect.ValueOf,获取变量的值,返回reflect.Value类型

reflect.Value.Kind,获取变量的类别,返回一个常量

reflect.Value.Interface(),转换成interface{}类型

1.1 获取变量类型

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	//反射数据类型
	t := reflect.TypeOf(i)
	fmt.Println("类型是", t)
	//反射数据值
	v := reflect.ValueOf(i)
	fmt.Println("值是", v)
}

func main() {
	a := "hello"
	Test(a)
}

输出结果如下

类型是 string
值是 hello

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	//反射获取类型
	t := reflect.TypeOf(i)
	fmt.Println("类型是", t)

	//反射获取值
	v := reflect.ValueOf(i)
	//判断值的类别
	c := v.Kind()
	fmt.Println("类别是", c)
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 80,
	}
	Test(stu)

	fmt.Println("-------------")
	var num int = 10
	Test(num)
}

输出结果如下

类型是 main.Student
类别是 struct
-------------
类型是 int
类别是 int

1.2 断言处理类型转换

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	t := reflect.TypeOf(i)
	fmt.Println("类型是", t)

	//类别
	v := reflect.ValueOf(i)
	c := v.Kind()
	fmt.Println("类别是", c)
	fmt.Printf("c的类型是%T\n", c)
	fmt.Printf("v的类型是%T\n", v)

	//转换成接口
	iv := v.Interface()
	fmt.Printf("iv的类型%T\n", iv)
	//断言处理
	stu_iv, err := iv.(Student)
	if err {
		fmt.Printf("stu_iv的类型%T\n", stu_iv)
	}
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 80,
	}
	Test(stu)

}

输出结果如下

类型是 main.Student
类别是 struct
c的类型是reflect.Kind
v的类型是reflect.Value
iv的类型main.Student
stu_iv的类型main.Student

2. ValueOf

2.1 获取变量值

reflect.valueof(x).Float()

reflect.valueof(x).Int()

reflect.valueof(x).String()

reflect.Valueof(x).Bool()

2.2 类型转换

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	fmt.Printf("v的类型是%T\n", v)
	//转换成指定类型
	t := v.Int()
	fmt.Printf("t的类型是%T\n", t)
}

func main() {
	//类型不同的话会报错
	var num int = 100
	Test(num)
}

输出结果如下

v的类型是reflect.Value
t的类型是int64

3. Value.Set

3.1 设置变量值

reflect.Value.SetFloat(),设置浮点数

reflect.value.SetInt(),设置整数

reflect.Value.SetString(),设置字符串

3.2 示例

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//更新值需要value的地址,否则会保存,Elem()表示指针*
	v.Elem().SetInt(100)
	result := v.Elem().Int()
	fmt.Printf("result类型为 %T, 值为 %d\n", result, result)
}

func main() {
	var num int = 10
	Test(&num)
}

输出结果如下

result类型为 int64, 值为 100

4. 结构体反射

反射出结构体的属性和方法数量

方法名需大写,需要被跨包调用识别

4.1 查看结构体字段数量和方法数量

package main

import (
	"fmt"
	"reflect"
)

//结构体
type Student struct {
	Name  string
	Age   int
	Score float32
}

//结构体方法
func (s Student) Run() {
	fmt.Println("Running")
}

func (s Student) Sleep() {
	fmt.Println("Sleeping")
}

//使用反射查看结构体字段数量和方法数量
func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//类别判断
	if v.Kind() != reflect.Struct {
		fmt.Println("不是结构体")
		return
	}
	//获取结构体中字段数量
	stu_num := v.NumField()
	fmt.Println("字段数量: ", stu_num)
	//获取结构体中方法数量
	stu_meth := v.NumMethod()
	fmt.Println("方法数量: ", stu_meth)
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 88,
	}
	Test(stu)
}

输出结果如下

字段数量:  3
方法数量:  2

4.2 获取结构体属性

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//获取结构体中每个属性
	for i := 0; i < v.NumField(); i++ {
		//输出属性值
		fmt.Printf("%d %v\n", i, v.Field(i))
		//输出属性值的类型
		fmt.Printf("%d %v\n", i, v.Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 88,
	}
	Test(stu)
}

输出结果如下

0 张三
0 string
1 18
1 int
2 88
2 float32

4.3 更改属性值

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}, name string) {
	v := reflect.ValueOf(i)
	vk := v.Kind()
	//判断是都为指针并指向结构体类型
	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//更改属性值
	v.Elem().Field(0).SetString(name)
	//获取结构体中每个属性
	for i := 0; i < v.Elem().NumField(); i++ {
		//输出属性值
		fmt.Printf("%d %v\n", i, v.Elem().Field(i))
		//输出属性值的类型
		fmt.Printf("%d %v\n", i, v.Elem().Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 88,
	}
	Test(&stu, "李四")
}

输出结果如下

0 李四
0 string
1 18
1 int
2 88
2 float32

4.4 Tag原信息处理

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type Student struct {
	Name  string `json:"stu_name"`
	Age   int
	Score float32
}

func Test(i interface{}, name string) {
	v := reflect.ValueOf(i)
	vk := v.Kind()
	//判断是都为指针并指向结构体类型
	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//更改属性值
	v.Elem().Field(0).SetString(name)
	//获取结构体中每个属性
	for i := 0; i < v.Elem().NumField(); i++ {
		//输出属性值
		fmt.Printf("%d %v\n", i, v.Elem().Field(i))
		//输出属性值的类型
		fmt.Printf("%d %v\n", i, v.Elem().Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "张三",
		Age:   18,
		Score: 88,
	}
	Test(&stu, "李四")
	fmt.Println("----------------json原信息----------------")
	result, _ := json.Marshal(stu)
	fmt.Println("json原信息: ", string(result))
	//反射获取类型
	st := reflect.TypeOf(stu)
	s := st.Field(0)
	fmt.Printf("Name原信息名称: %s\n", s.Tag.Get("json"))
}

输出结果如下

0 李四
0 string
1 18
1 int
2 88
2 float32
----------------json原信息----------------
json原信息:  {"stu_name":"李四","Age":18,"Score":88}
Name原信息名称: stu_name

5. 函数反射

Go 中函数是可以赋值给变量的

示例:

既然函数可以像普通的类型变量一样,那么在反射机制中就和不同的变量是一样的,在反射中函数和方法的类型(Type)都是reflect.Func,如果要调用函数,通过 Value 的Call() 方法

package main

import (
	"fmt"
	"reflect"
)

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

func main() {
	//反射使用函数
	v := reflect.ValueOf(hello)
	//类型判断是否属于reflect.func类型
	if v.Kind() == reflect.Func {
		fmt.Println("函数")
	}
	//反射调用函数
	v.Call(nil)   //Call中需要传入的是切片
}

输出结果如下

函数
hello world

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射调用传参和返回值函数
func Test(i int) string {
	return strconv.Itoa(i)
}

func main() {
	v := reflect.ValueOf(Test)
	//定义参数切片
	params := make([]reflect.Value, 1)
	//切片元素赋值
	params[0] = reflect.ValueOf(20)
	//反射调函数
	result := v.Call(params)
	fmt.Printf("result的类型是 %T\n", result)
	//[]reflect.Value切片转换string
	s := result[0].Interface().(string)
	fmt.Printf("s的类型是 %T ,值为 %s\n", s, s)
}

输出结果如下

result的类型是 []reflect.Value
s的类型是 string ,值为 20

6. 方法反射

反射中方法的调用,函数和方法可以说其实本质上是相同的,只不过方法与一个“对象”进行了“绑定”,方法是“对象”的一种行为,这种行为是对于这个“对象”的一系列操作,例如修改“对象”的某个属性

6.1 使用 MethodByName 名称调用方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射方法
type Student struct {
	Name string
	Age  int
}

//结构体方法
func (s *Student) SetName(name string) {
	s.Name = name
}

func (s *Student) SetAge(age int) {
	s.Age = age
}

func (s *Student) String() string {
	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)
}

func main() {
	//实例化
	stu := &Student{"张三", 19}
	//反射获取值:指针方式
	stuV := reflect.ValueOf(&stu).Elem()
	fmt.Println("修改前: ", stuV.MethodByName("String").Call(nil)[0])
	//修改值
	params := make([]reflect.Value, 1)		//定义切片
	params[0] = reflect.ValueOf("李四")
	stuV.MethodByName("SetName").Call(params)
	params[0] = reflect.ValueOf(20)
	stuV.MethodByName("SetAge").Call(params)
	fmt.Println("修改后: ", stuV.MethodByName("String").Call(nil)[0])
}

输出结果如下

修改前:  0xc000004078,Name:张三,Age:19
修改后:  0xc000004078,Name:李四,Age:20

6.2 使用 method 索引调用方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射方法
type Student struct {
	Name string
	Age  int
}

//结构体方法
func (s *Student) B(name string) {
	s.Name = name
}

func (s *Student) A(age int) {
	s.Age = age
}

func (s *Student) C() string {
	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)
}

func main() {
	//实例化
	stu := &Student{"张三", 19}
	//反射获取值:指针方式
	stuV := reflect.ValueOf(&stu).Elem()

	//索引调用方法
	fmt.Println("修改前: ", stuV.Method(2).Call(nil)[0])

	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf("李四")
	stuV.Method(1).Call(params)

	params[0] = reflect.ValueOf(20)
	stuV.Method(0).Call(params)
	fmt.Println("修改后: ", stuV.Method(2).Call(nil)[0])
	//调用索引大小取决于方法名称的ASCII大小进行排序
}

输出结果如下

修改前:  0xc000004078,Name:张三,Age:19
修改后:  0xc000004078,Name:李四,Age:20

到此这篇关于Go语言学习之反射的用法详解的文章就介绍到这了,更多相关Go语言反射内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入理解Golang的反射reflect示例

    目录 编程语言中反射的概念 interface 和 反射 Golang的反射reflect reflect的基本功能TypeOf和ValueOf 说明 从relfect.Value中获取接口interface的信息 已知原有类型[进行“强制转换”] 说明 未知原有类型[遍历探测其Filed] 说明 通过reflect.Value设置实际变量的值 说明 通过reflect.ValueOf来进行方法的调用 说明 Golang的反射reflect性能 小结 总结 参考链接 编程语言中反射的概念 在计算

  • Go系列教程之反射的用法

    反射是 Go 语言的高级主题之一.我会尽可能让它变得简单易懂. 本教程分为如下小节. 什么是反射? 为何需要检查变量,确定变量的类型? reflect 包 reflect.Type 和 reflect.Value reflect.Kind NumField() 和 Field() 方法 Int() 和 String() 方法 完整的程序 我们应该使用反射吗? 让我们来逐个讨论这些章节. 什么是反射? 反射就是程序能够在运行时检查变量和值,求出它们的类型.你可能还不太懂,这没关系.在本教程结束后,

  • 学习使用Go反射的用法示例

    什么是反射 大多数时候,Go中的变量,类型和函数非常简单直接.当需要一个类型.变量或者是函数时,可以直接定义它们: type Foo struct { A int B string } var x Foo func DoSomething(f Foo) { fmt.Println(f.A, f.B) } 但是有时你希望在运行时使用变量的在编写程序时还不存在的信息.比如你正在尝试将文件或网络请求中的数据映射到变量中.或者你想构建一个适用于不同类型的工具.在这种情况下,你需要使用反射.反射使您能够在

  • Go语言学习笔记之反射用法详解

    本文实例讲述了Go学习笔记之反射用法.分享给大家供大家参考,具体如下: 一.类型(Type) 反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥(mi)补了静态语言在动态行为上的不足.同时,反射还是实现元编程的重要手段. 和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的.反射操作所需要的全部信息都源自接口变量.接口变量除存储自身类型外,还会保存实际对象的类型数据. func TypeOf(i interface{

  • golang 如何通过反射创建新对象

    废话少说,直接贴代码~ type A struct { Name string } // 测试unit func TestReflect(t *testing.T) { reflectNew((*A)(nil)) } //反射创建新对象. func reflectNew(target interface{}) { if target == nil { fmt.Println("参数不能未空") return } t := reflect.TypeOf(target) if t.Kind

  • Go语言基础反射示例详解

    目录 概述 语法 一.基本操作 二.修改目标对象 三.动态调用方法 总结 示例 概述 在程序运行期对程序动态的进行访问和修改 reflect godoc: https://golang.org/pkg/reflect/ reflect包有两个数据类型: Type:数据类型 [reflect.TypeOf():是获取Type的方法] Value:值的类型[reflect.ValueOf():是获取Value的方法] 语法 一.基本操作 获取变量类型 func TypeOf(i interface{

  • Go语言学习之反射的用法详解

    目录 1. reflect 包 1.1 获取变量类型 1.2 断言处理类型转换 2. ValueOf 2.1 获取变量值 2.2 类型转换 3. Value.Set 3.1 设置变量值 3.2 示例 4. 结构体反射 4.1 查看结构体字段数量和方法数量 4.2 获取结构体属性 4.3 更改属性值 4.4 Tag原信息处理 5. 函数反射 6. 方法反射 6.1 使用 MethodByName 名称调用方法 6.2 使用 method 索引调用方法 反射指的是运行时动态的获取变量的相关信息 1.

  • Go语言学习之数组的用法详解

    目录 引言 一.数组的定义 1. 语法 2. 示例 二.数组的初始化 1. 未初始化的数组 2. 使用初始化列表 3. 省略数组长度 4. 指定索引值的方式来初始化 5. 访问数组元素 6. 根据数组长度遍历数组 三. 访问数组元素 1. 访问数组元素 2. 根据数组长度遍历数组 四.冒泡排序 五.多维数组 1. 二维数组 2. 初始化二维数组 3. 访问二维数组 六.向函数传递数组 1. 形参设定数组大小 2. 形参未设定数组大小 3. 示例 总结 引言 数组是相同数据类型的一组数据的集合,数

  • Go语言学习之指针的用法详解

    目录 引言 一.定义结构体 1. 语法格式 2. 示例 二.访问结构体成员 三.结构体作为函数参数 四.结构体指针 总结 引言 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合 结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性: Title :标题 Author : 作者 Subject:学科 ID:书籍ID 一.定义结构体 1. 语法格式 结构体定义需要使用 type 和 struc

  • ES6学习教程之Promise用法详解

    前言 promise用了这么多年了,一直也没有系统整理过.今天整理整理promise的相关东西,感兴趣的可以一起看一看.我尽量用更容易理解的语言来剖析一下promise 我准备分两篇文章来说明一下promise 一篇来理解和使用promise(本篇) 另一篇来从promise使用功能的角度来剖析下promise的源码(下一篇) 1.什么是Promise 我的理解是:实现让我们用同步的方式去写异步代码的一种技术.是异步解决方案的一种. 他可以将多个异步操作进行队列化,让它们可以按照我们的想法去顺序

  • C语言学习之关键字的示例详解

    目录 1. 前言 2. 什么是关键字 3. extern-声明外部符号 4. auto-自动 5. typedef-类型重定义(类型重命名) 6. register-寄存器 6.1 存储器 6.2 register关键字的作用 7. static-静态 7.1 static修饰局部变量 7.2 static修饰全局变量 7.3 static修饰函数 1. 前言 大家好,我是努力学习游泳的鱼.关键字,这名字一听,就很关键.而有些关键字,你可能不是很了解,更别谈使用.所以,这篇文章将带你见识常见的关

  • C语言学习之指针的使用详解

    目录 一.指针概念 1.指针变量 2.指针类型 3.二级指针 二.野指针 1.野指针成因 2.规避野指针 三.指针运算 1.指针±整数 2.指针-指针 3.指针关系运算 四.指针数组 1.指针和数组 2.指针数组的概念 五.字符指针 六.数组指针 七.数组传参和指针传参 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 八.函数指针 九.函数指针数组 十.回调函数 一.指针概念 在学习指针之前我们先要了解一下内存,内存是存储区域,我们可以把内存划分成一个一个的内存单元,最小

  • C语言switch使用之诡异用法详解

    关于switch的用法这里不再做什么总结了,其实这个是一个便捷的快速跳转条件切换器.而关于这个功能最常用的技术讨论点在于case后面的break以及default.这里不讨论这些,直接看下面的代码: #include"stdio.h" int main(void) { int num = 0; switch(num) { printf("functionrun!\n"); } return 0; } 上面的代码中用到了一个switch,但是代码块中没有任何case以

  • C语言fgetc和fputc函数用法详解(以字符形式读写文件)

    在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块).本节介绍以字符形式读写文件. 以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符.主要使用两个函数,分别是 fgetc() 和 fputc(). 字符读取函数 fgetc fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符.fgetc() 的用法为: int fgetc (FILE *fp); fp 为文件指针.fgetc() 读

  • R语言学习笔记之lm函数详解

    在使用lm函数做一元线性回归时,发现lm(y~x+1)和lm(y~x)的结果是一致的,一直没找到两者之间的区别,经过大神们的讨论和测试,才发现其中的差别,测试如下: ------------------------------------------------------------- ------------------------------------------------------------- 结果可以发现,两者的结果是一样的,并无区别,但是若改为lm(y~x-1)就能看出+1和

  • Go语言学习之条件语句使用详解

    目录 1.if...else判断语法 2.if嵌套语法 3.switch语句 4.类型switch语句 5.fallthrough关键字使用 小结 1.if...else判断语法 语法的使用和其他语言没啥区别. 样例代码如下: // 判断语句 func panduan(a int) { if a > 50 { fmt.Println("a > 50") } else if a < 30 { fmt.Println("a < 30") } el

随机推荐