有时要求写一个函数有能力统一处理各种值类型的函数,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在设计函数时并不存在,当我们无法透视一个未知类型的布局时,这段代码就无法继续,这是就需要反射

reflect.Type & reflect.Value

反射功能由reflect包提供,其中定义了两个重要类型: Type 和 Value,Type表示Go语言的一个类型,它是一个有很多方法的接口,这些方法可以用来识别类型以及透视类型的组成部分,比如一个结构的各个字段或者一个函数的各个参数

reflect.Type接口只有一个实现,即类型描述符,接口值中的动态类型也是类型描述符号

t := reflect.TypeOf(3)
fmt.Println(t.String()) // int
fmt.Println(t) // int

reflect.TypeOf函数接收任何的interface{}参数,并且将接口中的动态类型以reflect.Type形式返回,将一个具体值赋给一个接口类型时会发生一个隐式类型转换,转换会生成一个包含两部分内容的接口值: 动态类型部分是操作数类型(int),动态值部分是操作数值(3),并且TypeOf返回一个接口值对应的动态类型,返回总是具体类型

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // *os.File

上述代码输出的是*os.File而不是io.Writer

reflect.Value可以包含一个任意类型的值,reflect.ValueOf函数接收任意的interface{}并将接口的动态值以reflect.Value的形式返回,返回值同样是具体值,不过包含一个接口值

v := reflect.ValueOf(3)
fmt.Println(v) // 3
fmt.Printf("%v\n", v) // 3
fmt.Println(v.String()) // <int value>

调用Value的Type方法会将其类型以reflect.Type方式返回

v := reflect.ValueOf(3)
t := v.Type()
fmt.Println(t.String()) // int

reflect.Value的逆操作是reflect.Value.Interface方法,返回一个interface{}接口值,与reflect.Value包含一个具体值

v := reflect.ValueOf(3)
x := v.Interface()
i := x.(int)
fmt.Printf("%d\n", i)

reflect.Value和interface{}都可以任意的值,二者的区别是空接口(interface{}),隐藏了值的布局信息、内置操作和相关方法,所以除非知道其动态类型,并用一个类型断言来渗透进去,否则对所包含值能做的事情很少,Value有很多方法可以用来分析所包含的值,而不用知道它的类型,基于此可以尝试写一个通用的格式化函数:

package format

import (
"reflect"
"strconv"
) func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
} func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 8)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + "0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
default:
return v.Type().String() + " value"
}
}

调用:

func main() {
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x))
fmt.Println(format.Any(d))
fmt.Println(format.Any([]int64{x}))
fmt.Println(format.Any([]time.Duration{d}))
}

输出:

1
1
[]int640xc0000200f8
[]time.Duration0xc000020110

值显示

实现一个函数Display,对于给定的任意一个复杂值x,输出这个复杂值的完整结构,并对找到的每个元素标上这个元素的路径

func Display(name string, x interface{}) {
fmt.Printf("Display %s (%T):\n", name, x)
display(name, reflect.ValueOf(x))
} func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf("%s = invalid\n", path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s %s", path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
display(fmt.Sprintf("(*%s)", path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
display(path+".value", v.Elem())
}
default:
fmt.Printf("%s = %s\n", path, formatAtom(v))
}
}

在上述display函数中,我们使用之前定义的formatAtom函数来输出基础值(基础类型、函数和通道),使用reflect.Value的一些方法来递归展示复杂类型的每个组成部分,当递归深入时,path字符串会增长,以表示如何找到当前值

其中Len方法会返回slice或者数组中元素个数,NumField可以报告结果中的字段数,Field(i)会返回第i个字段,返回字段类型为reflect.Value,MapKeys方法返回一个元素类型为reflect.Value的slice, 每个元素都是一个map的键,Elem方法返回指针指向的变量,这个方法在指针是nil时也能正确处理,返回的结果属于Invalid类型,所以用IsNil来显式检测

测试:

func main() {
strangelove := Movie{
Title: "Dr.Strangelove",
Subtitle: "How I learned to Stop Worring and Love the Bomb",
Year: 1964,
Color: false,
Actor: map[string]string{
"Dr.Strangelove": "Peter Sellers",
"Grp.Capt. Lionel Mandrake": "Peter Sellers",
"Gen. Buck Turgidson": "George C.Scott",
},
Oscars: []string{
"Best Actor (Nomin.)",
"Best Adapted Screenplay (Nomin.)",
},
}
format.Display("strangelove", strangelove)
}

输出:

Display strangelove (main.Movie):
strangelove Title = "Dr.Strangelove"
strangelove Subtitle = "How I learned to Stop Worring and Love the Bomb"
strangelove Year = 3654
strangelove Color = false
strangelove Actor["Dr.Strangelove"] = "Peter Sellers"
strangelove Actor["Grp.Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove Actor["Gen. Buck Turgidson"] = "George C.Scott"
strangelove Oscars[0] = "Best Actor (Nomin.)"
strangelove Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove Sequel = nil

设置值

一个变量是一个可寻址的存储区域,其中包含了一个值,并且其值可以通过这个地址进行更新

x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()

reflect.ValueOf返回的reflect.Value都是不可寻址的,但d是通过c的指针得来的,所以是可寻址的,通过如下方法来询问是否可寻址

fmt.Println(a.CanAddr(), b.CanAddr(), c.CanAddr(), d.CanAddr()) // false false false true

可直接通过reflect.Value来更新变量,无须通过指针,而是reflect.Set方法

x := 2
d := reflect.ValueOf(&x).Elem()
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4

Go语言核心知识回顾(反射)的更多相关文章

  1. C#基础知识回顾-- 反射(1)

    C#基础知识回顾-- 反射(1)   反射(reflection)是一种允许用户获得类型信息的C#特性.术语“反射”源自于它的工作方式: Type对象映射它所代表的底层对象.对Type对象进行查询可以 ...

  2. Docker 核心知识回顾

    Docker 核心知识回顾 最近公司为了提高项目治理能力.提升开发效率,将之前的CICD项目扩展成devops进行项目管理.开发人员需要对自己的负责的项目进行流水线的部署,包括写Dockerfile ...

  3. C#基础知识回顾-- 反射(3)

    C#基础知识回顾-- 反射(3)   获取Type对象的构造函数: 前一篇因为篇幅问题因为篇幅太短被移除首页,反射这一块还有一篇“怎样在程序集中使用反射”, 其他没有什么可以写的了,前两篇主要是铺垫, ...

  4. C#基础知识回顾-- 反射(4)

    从程序集获得类型 先说点题外话,现在技术真的发展太快了.我这边还很多东西半生不熟 呢,那边又出现了好多有趣的新东西让你眼红不已.学还是不学这还真是 个问题.Node.js .bootstrap,我最近 ...

  5. C#基础知识回顾-- 反射(2)

    使用反射调用方法: 一旦知道一个类型所支持的方法,就可以对方法进行调用.调用时,需使用包含在   MethodInfo中的Invoke()方法.调用形式:   object Invoke(object ...

  6. 复习笔记——1. C语言基础知识回顾

    1. 数据类型 1.1 基本数据类型 整型:int, long,unsigned int,unsigned long,long long-- 字符型:char 浮点型:float, double-- ...

  7. python---基础知识回顾(六)网络编程

    python---基础知识回顾(十)进程和线程(进程) python---基础知识回顾(十)进程和线程(多线程) python---基础知识回顾(十)进程和线程(自定义线程池) 一:Socket (一 ...

  8. [转帖]java注解核心知识总结

    java注解核心知识总结 2019-11-01 20:39:50 从事Java 阅读数 2  收藏 文章标签: java注解总结程序员 更多 分类专栏: java 注解   版权声明:本文为博主原创文 ...

  9. Go语言核心36讲(Go语言实战与应用十八)--学习笔记

    40 | io包中的接口和工具 (上) 我们在前几篇文章中,主要讨论了strings.Builder.strings.Reader和bytes.Buffer这三个数据类型. 知识回顾 还记得吗?当时我 ...

  10. Go语言核心36讲42-----io包中接口的好处与优势

    我们在前几篇文章中,主要讨论了strings.Builder.strings.Reader和bytes.Buffer这三个数据类型. 知识回顾 还记得吗?当时我还问过你"它们都实现了哪些接口 ...

随机推荐

  1. AT212 P-CASカードと高橋君

    题目描述 高桥君为了准备即将到来的7月27日土用丑日,打算邮购一些高级鳗鱼食材,通过网上银行来支付. 高桥君使用的银行卡背面有下图所示的9×9密码表.支付的时候从表中某一位置开始根据指定的方向连续读4 ...

  2. List,Set,Map存取元素各有什么特点 hashMap、hashTable的区别 Arraylist和linkedList的区别

    1.List,Set,Map存取元素各有什么特点? 1.存放 (1)List存放元素是有序,可重复 (2)Set存放元素无序,不可重复 (3)Map元素键值对形式存放,键无序不可重复,值可重复 2.取 ...

  3. 【再学WPF】模板

    1 <!--设置所有的按钮样式--> 2 <Style TargetType="Button"> 3 <Setter Property="M ...

  4. 日志注解,基于ruoyi的后置切面改进而来

    有次接口响应时间太长,想知道具体接口执行的时间是多少,于是决定通过注解来实现这个想法,刚好ruoyi本身就提供了完善的日志注解,虽然是采用后置通知,但是完全不影响我们改造它. 想要实现接口耗时的功能, ...

  5. Windows下Zookeeper安装使用

    Windows下Zookeeper安装使用 ZooKeeper是一种分布式协调服务,用于管理大型主机. 在分布式环境中协调和管理服务是一个复杂的过程. ZooKeeper通过其简单的架构和API解决了 ...

  6. JDMasking v0.1.0-beta 发布

    JDMasking 全称是jdbc data masking,是一款零代码修改.无重启.基于JDBC的动态数据脱敏软件. JDMasking 主要对实现jdbc的驱动进行字节码的增强,支持对运行中的程 ...

  7. VSCode 开发Vue + ElementUI

    参考 (1)VSCode 开发Vue + ElementUI (2)玩转VSCode-完整构建VSCode开发调试环境 (shuzhiduo.com) (3)使用vscode搭建vue项目并引用ele ...

  8. IP与bigint互转

    IP转为bigint create function [dbo].[iptobigint](@ipinfo varchar(16)) returns bigint as begin declare @ ...

  9. Ubuntu 14.04环境编译android源码android-5.0.2_r1.7z

    环境: Win7:8G内存 vmware:vm给ubuntu分配4G内存80G空间 参考视频: https://www.bilibili.com/video/BV15t411R78o ubuntu14 ...

  10. 微信小程序分享出去的页面再点进来,如何取值并且在新用户未授权的情况下,授权后跳到当前页面

    1.如何点击分享的页面进来,授权后跳转到当前页面 可以在授权成功后,将openid.头像.昵称入库成功之后,标记一下,及getStorageSync // 通过code获取openid getUser ...