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

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. 艾思最新案例分享:塔蓝物流app-物流仓储管理系统app. app开发

    塔蓝物流app是一款物流仓储管理app:主要业务范围空运,海运,进出口货物及过境货物的运输代理,包括揽物订舱,仓储(危险品除外),包装,搬运装卸,中转,流通加工,集装箱拼装拆箱(危险品除外),结算运杂 ...

  2. CatDCGAN项目复现与对抗网络初识

    CatDCGAN项目复现与对抗网络初识 作者 CarpVexing 日期 100521 禁止转载 目录 CatDCGAN项目复现与对抗网络初识 引言 CatDCGAN项目基本信息 复现项目的准备工作 ...

  3. Laravel自定义JSON错误消息 The given data was invalid.

    Laravel自定义错误消息 在Laravel验证请求时出现错误,默认会返回如下的错误: 可以看到JSO结构中的message为The given data was invalid.而并非是我们具体自 ...

  4. 003Java的诞生

    003Java的诞生 1.计算机语言发展史 (1)第一代语言 机器语言 我们都知道计算机的基本计算方式都是基于二进制的方式. 二进制:010111001010110010110100 这种代码是直接输 ...

  5. 代码还是那个代码,但我已经知道了hashmap背后的东西

    代码还是那个代码,但我已经知道了hashmap背后的东西 数据结构是链表的数组(注:后面的版本为了提升性能,已经是改成链表或者树(节点较多)了) 思想上是空间换时间的算法 构造函数上有容量和负载因子2 ...

  6. JavaScript argument

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 微信支付服务商api对接

    引入官方sdk <!--微信v3支付sdk {https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient}--> < ...

  8. 智汇成城 ,创赢未来 | AI+产业峰会智慧城市专场在深成功举办!

    11月4日下午,由福田区人才工作局指导,广州英码信息科技有限公司和共达地创新技术(深圳)有限公司联合主办,深圳市人工智能行业协会承办的AI+产业峰会之智慧城市专场活动在深圳市南山区成功举办. &quo ...

  9. [Unity移动端]真机调试

    一.Android Studio 1.log打印 打开AS,新建一个工程,点击左下角的Logcat,如下图,1是设备(支持模拟器,如果是真机的话,需要usb连接电脑,真机要是usb调试模式),2是包名 ...

  10. 记一下Linux环境SpringBoot 用OpenOffice Word转PDF

    环境 Windows或者Linux 首先安装 deb方式 tar -xvzf Apache_OpenOffice_XXXX_Linux_x86-64_install-deb_zh-CN.tar.gz ...