教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 golang 反射_Go进阶:反射3定律

golang 反射_Go进阶:反射3定律

发布时间:2022-01-11   编辑:jiaochengji.com
教程集为您提供golang 反射,Go进阶:反射3定律等资源,欢迎您收藏本站,我们将为您提供最新的golang 反射,Go进阶:反射3定律资源

原文作者:shitaibin

来源:简书

各位学习Go语言的朋友,周末好,这次跟大家聊一聊Go语言的一个高级话题:反射。

这篇文章是从我过去的学习笔记修改来的,内容主要来自Go Blog的一篇文章《The law of reflection》。

这篇文章主要介绍反射和接口的关系,解释内在的关系和原理

反射来自元编程,指通过类型检查变量本身数据结构的方式,只有部分编程语言支持反射。

<h2><span style="font-weight:bold;">类型</span></h2>

反射构建在类型系统之上,Go是静态类型语言,每一个变量都有静态类型,在编译时就确定下来了。

比如:

<pre class="has"><code>type MyInt int

var i int
var j MyInt</code></pre>

i和j的底层类型都是<code>int</code>,但i的静态类型是<code>int</code>,j的静态类型是<code>MyInt</code>,这两个是不同类型,是不能直接赋值的,需要类型强制转换。

接口类型比较特殊,接口类型的变量被多种对象类型赋值,看起来像动态语言的特性,但变量类型始终是接口类型,Go是静态的。举例:

<pre class="has"><code>var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on</code></pre>

虽然r被3种类型的变量赋值,但r的类型始终是<code>io.Reader</code>。

<blockquote>

最特别:空接口<code>interface{}</code>的变量可以被任何类型的值赋值,但类型一直都是<code>interface{}</code>。

</blockquote> <h2/> <h2><span style="font-weight:bold;">接口的表示</span></h2>

Russ Cox(Go语言创始人)在他的博客详细介绍了Go语言接口,结论是:

接口类型的变量存储的是一对数据

<ol><li>

变量实际的值

</li><li>

变量的静态类型

</li></ol>

例子:

<pre class="has"><code>var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty</code></pre>

r是接口类型变量,保存了值tty和tty的类型<code>*os.File</code>,所以才能使用类型断言判断r保存的值的静态类型:

<pre class="has"><code>var w io.Writer
w = r.(io.Writer)</code></pre>

虽然r中包含了tty和它的类型,包含了tty的所有函数,但r是接口类型,决定了r只能调用接口<code>io.Reader</code>中包含的函数。

记住:接口变量保存的不是接口类型的值,还是英语说起来更方便:Interfaces do not hold interface values.

<h2><span style="font-weight:bold;">反射的3条定律</span></h2> <h3><span style="font-weight:bold;">定律1:从接口值到反射对象</span></h3>

反射是一种检测存储在接口变量中值和类型的机制。通过<code>reflect</code>包的一些函数,可以把接口转换为反射定义的对象。

掌握<code>reflect</code>包的以下函数:

<ol><li>

<code>reflect.ValueOf({}interface) reflect.Value</code>:获取某个变量的值,但值是通过<code>reflect.Value</code>对象描述的。

</li><li>

<code>reflect.TypeOf({}interface) reflect.Type</code>:获取某个变量的静态类型,但值是通过<code>reflect.Type</code>对象描述的,是可以直接使用<code>Println</code>打印的。

</li><li>

<code>reflect.Value.Kind() Kind</code>:获取变量值的底层类型(类别),注意不是类型,是Int、Float,还是Struct,还是Slice,具体见此。

</li><li>

<code>reflect.Value.Type() reflect.Type</code>:获取变量值的类型,效果等同于<code>reflect.TypeOf</code>。

</li></ol>

再解释下Kind和Type的区别,比如:

<pre class="has"><code>type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)</code></pre>

v.Kind()得到的是Int,而Type得到是<code>MyInt</code>。

<h3><span style="font-weight:bold;">定律2:从反射对象到接口值</span></h3>

定律2是定律1的逆向过程,上面我们学了:<code>普通变量 -> 接口变量 -> 反射对象</code>的过程,这是从<code>反射对象 -> 接口变量</code>的过程,使用的是<code>Value</code>的<code>Interface</code>函数,是把实际的值赋值给空接口变量,它的声明如下:

<pre class="has"><code>func (v Value) Interface() (i interface{})</code></pre>

回忆一下:接口变量存储了实际的值和值的类型,<code>Println</code>可以根据接口变量实际存储的类型自动识别其值并打印。

注意事项:如果Value是结构体的非导出字段,调用该函数会导致panic。

<h3><span style="font-weight:bold;">定律3:当反射对象所存的值是可设置时,反射对象才可修改</span></h3>

从定律1入手理解,定律3就不再那么难懂。

Settability is a property of a reflection Value, and not all reflection Values have it.

可设置指的是,可以通过Value设置原始变量的值。

通过函数的例子思考一下可设置:

<pre class="has"><code>func f(x int)</code></pre>

在调用f的时候,传入了参数x,从函数内部修改x的值,外部的变量的值并不会发生改变,因为这种是传值,是拷贝的传递方式。

<pre class="has"><code>func f(p *int)</code></pre>

函数f的入参是指针类型,在函数内部的修改变量的值,函数外部变量的值也会跟着变化。

使用反射也是这个原理,如果创建Value时传递的是变量,则Value是不可设置的。如果创建Value时传递的是变量地址,则Value是可设置的。

可以使用<code>Value.CanSet()</code>检测是否可以通过此Value修改原始变量的值。

<pre class="has"><code>x := 10
v1 := reflect.ValueOf(x)
fmt.Println("setable:", v1.CanSet())
p := reflect.ValueOf(&x)
fmt.Println("setable:", p.CanSet())
v2 := p.Elem()
fmt.Println("setable:", v2.CanSet())</code></pre>

如何通过Value设置原始对象值呢?

<code>Value.SetXXX()</code>系列函数可设置Value中原始对象的值。

系列函数有:

<ul><li>

Value.SetInt()

</li><li>

Value.SetUint()

</li><li>

Value.SetBool()

</li><li>

Value.SetBytes()

</li><li>

Value.SetFloat()

</li><li>

Value.SetString()

</li><li>

...

</li></ul>

设置函数这么多,到底该选用哪个Set函数?
根据<code>Value.Kind()</code>的结果去获得变量的底层类型,然后选用该类别的Set函数。

<h2><span style="font-weight:bold;">参考资料</span></h2> <ol><li>

https://blog.golang.org/laws-of-reflection

</li></ol>

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

Golang语言社区

ID:Golangweb

 www.GolangWeb.com

游戏服务器架构丨分布式技术丨大数据丨游戏算法学习

到此这篇关于“golang 反射_Go进阶:反射3定律”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang 反射_Go进阶:反射3定律
Go语言学习之reflect包(The way to go)
golang的反射机制与实践(上)
C#反射的一些基本应用
Go 编程:图解反射
Go语言--反射(reflect)
图解 Go 反射实现原理
go 获取函数地址_Go语言基础--接口浅析
C#反射实例学习入门及注意事项
golang反射——执行函数

[关闭]
~ ~