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

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. CentOS安装并查看lm_sensors CPU温度监控

    CentOS安装并查看lm_sensors 首先查看是否安装rpm包: [root@localhost home]# rpm -qa|grep sensors lm_sensors-libs-3.1. ...

  2. 微信小程序—标题栏

    <template> <view :style="{paddingTop: statusBarHeight + 'px',background:bg}" clas ...

  3. Vue+SSM+Element-Ui实现前后端分离(2)

    前言:后台使用ssm搭建,对以前学习知识的一个回顾,与此同时来发现自己不足.这里主要采用配置文件方式进行,有部分注解. 目标:搭建ssm框架,并测试成功:(其中也有aop切面的编写) 一.开发工具 I ...

  4. What is RSS

    What is RSS?RSS (Rich Site Summary) is a format for delivering regularly changing web content. Many ...

  5. PASS模型需求分析和原型设计

    班级网址 https://edu.cnblogs.com/campus/zjcsxy/SE2020 作业要求 https://edu.cnblogs.com/campus/zjcsxy/SE2020/ ...

  6. 新的世界,我们推荐不劳而获 -> 持续更新中

    随着技术带来的生产力爆发越来越猛烈,有人提出是不是有必要保留一些落后的生产工艺及相关岗位,以避免社会动荡. 我的答案:不用.但是要改变社会对于不劳而获的态度:我们对于生活资料的不劳而获持接受的态度,但 ...

  7. SQLyog中创建的数据库在idea找不到

    在里面把需要的数据库

  8. Windows下fmt库的链接与使用

    下载源码. 使用mingw编译源码.注意设置cmake文件的产生路径.pkgconfig文件的产生路径(windows下用不到产生的pc文件).库的安装路径. make -j8 install. 新建 ...

  9. (转载)一篇文章详解python的字符编码问题

    一篇文章详解python的字符编码问题   一:什么是编码 将明文转换为计算机可以识别的编码文本称为"编码".反之从计算机可识别的编码文本转回为明文为"解码". ...

  10. Python学习笔记--函数来啦!

    函数 函数,就是组织好的,可重复使用的,用来实现特定功能的代码块 实际的小案例:不使用内置函数len,利用函数知识计算出字符串的长度 实现: 函数的基础定义语法 案例:自动查核酸 实现: 函数的传入参 ...