• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell
您的位置:首页 > 脚本语言 >Golang > 谈谈Go语言的反射三定律

谈谈Go语言的反射三定律

作者:daisy 字体:[增加 减小] 来源:互联网

daisy 通过本文主要向大家介绍了本福特定律r语言检测,谈谈tf少年go,谈谈学习语言的重要性,谈谈对语言规范的认识,谈谈对网络语言的看法等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

简介

Reflection(反射)在计算机中表示 程序能够检查自身结构的能力,尤其是类型。它是元编程的一种形式,也是最容易让人迷惑的一部分。

虽然Go语言没有继承的概念,但为了便于理解,如果一个struct A 实现了 interface B的所有方法时,我们称之为“继承”。

类型和接口

反射建立在类型系统之上,因此我们从类型基础知识说起。

Go是静态类型语言。每个变量都有且只有一个静态类型,在编译时就已经确定。比如 int、float32、*MyType、[]byte。 如果我们做出如下声明:

type MyInt int

var i int
var j MyInt
</div>

上面的代码中,变量 i 的类型是 int,j 的类型是 MyInt。 所以,尽管变量 i 和 j 具有共同的底层类型 int,但它们的静态类型并不一样。不经过类型转换直接相互赋值时,编译器会报错。

关于类型,一个重要的分类是 接口类型(interface),每个接口类型都代表固定的方法集合。一个接口变量就可以存储(或“指向”,接口变量类似于指针)任何类型的具体值,只要这个值实现了该接口类型的所有方法。一组广为人知的例子是 io.Reader 和 io.Writer, Reader 和 Writer 类型来源于 io包,声明如下:

// Reader is the interface that wraps the basic Read method.
type Reader interface {
 Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
 Write(p []byte) (n int, err error)
}
</div>

任何实现了 Read(Write)方法的类型,我们都称之为继承了 io.Reader(io.Writer)接口。换句话说, 一个类型为 io.Reader 的变量 可以指向(接口变量类似于指针)任何类型的变量,只要这个类型实现了Read 方法:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
</div>

要时刻牢记:不管变量 r 指向的具体值是什么,它的类型永远是 io.Reader。再重复一次:Go语言是静态类型语言,变量 r 的静态类型是 io.Reader。

一个非常非常重要的接口类型是空接口,即:

interface{}
</div>

它代表一个空集,没有任何方法。由于任何具体的值都有 零或更多个方法,因此类型为interface{} 的变量能够存储任何值。

有人说,Go的接口是动态类型的。这个说法是错的!接口变量也是静态类型的,它永远只有一个相同的静态类型。如果在运行时它存储的值发生了变化,这个值也必须满足接口类型的方法集合。

由于反射和接口两者的关系很密切,我们必须澄清这一点。

接口变量的表示

Russ Cox 在2009年写了一篇文章介绍 Go中接口变量的表示形式,这里我们不需要重复所有的细节,只做一个简单的总结。

Interface变量存储一对值:赋给该变量的具体的值、值类型的描述符。更准确一点来说,值就是实现该接口的底层数据,类型是底层数据类型的描述。举个例子:

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

在这个例子中,变量 r 在结构上包含一个 (value, type) 对:(tty, os.File)。注意:类型 os.File 不仅仅实现了 Read 方法。虽然接口变量只提供 Read 函数的调用权,但是底层的值包含了关于这个值的所有类型信息。所以我们能够做这样的类型转换:

var w io.Writer
w = r.(io.Writer)
</div>

上面代码的第二行是一个类型断言,它断定变量 r 内部的实际值也继承了 io.Writer接口,所以才能被赋值给 w。赋值之后,w 就指向了 (tty, *os.File) 对,和变量 r 指向的是同一个 (value, type) 对。不管底层具体值的方法集有多大,由于接口的静态类型限制,接口变量只能调用特定的一些方法。

我们继续往下看:

var empty interface{}
empty = w
</div>

这里的空接口变量 empty 也包含 (tty, *os.File) 对。这一点很容易理解:空接口变量可以存储任何具体值以及该值的所有描述信息。

细心的朋友可能会发现,这里没有使用类型断言,因为变量 w 满足 空接口的所有方法(传说中的“无招胜有招”)。在前一个例子中,我们把一个具体值 从 io.Reader 转换为 io.Writer 时,需要显式的类型断言,是因为 io.Writer 的方法集合 不是 io.Reader 的子集。

另外需要注意的一点是,(value, type) 对中的 type 必须是 具体的类型(struct或基本类型),不能是 接口类型。 接口类型不能存储接口变量。

关于接口,我们就介绍到这里,下面我们看看Go语言的反射三定律。

反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”。

注:这里反射类型指 reflect.Type 和 reflect.Value。

从用法上来讲,反射提供了一种机制,允许程序在运行时检查接口变量内部存储的 (value, type) 对。在最开始,我们先了解下 reflect 包的两种类型:Type 和 Value。这两种类型使访问接口内的数据成为可能。它们对应两个简单的方法,分别是 reflect.TypeOf 和 reflect.ValueOf,分别用来读取接口变量的 reflect.Type 和 reflect.Value 部分。当然,从 reflect.Value 也很容易获取到 reflect.Type。目前我们先将它们分开。

首先,我们下看 reflect.TypeOf:

package main

import (
 "fmt"
 "reflect"
)

func main() {
 var x float64 = 3.4
 fmt.Println("type:", reflect.TypeOf(x))
}
</div>

这段代码会打印出:

type: float64
</div>

你可能会疑惑:为什么没看到接口?这段代码看起来只是把一个 float64类型的变量 x 传递给 reflect.TypeOf,并没有传递接口。事实上,接口就在那里。查阅一下TypeOf 的文档,你会发现 reflect.TypeOf 的函数签名里包含一个空接口:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
</div>

我们调用 reflect.TypeOf(x) 时,x 被存储在一个空接口变量中被传递过去; 然后reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。

函数 reflect.ValueOf 也会对底层的值进行恢复(这里我们忽略细节,只关注可执行的代码):

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))
</div>

上面这段代码打印出:

value: <float64 Value>
</div>

类型 reflect.Type 和 reflect.Value 都有很多方法,我们可以检查和使用它们。这里我们举几个例子。类型 reflect.Value 有一个方法 Type(),它会返回一个 reflect.Type 类型的对象。Type和 Value都有一个名为 Kind 的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice等。Value类型也有一些类似于Int、Float的方法,用来提取底层的数据。Int方法用来提取 int64, Float方法用来提取 float64,参考下面的代码:

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
</div>

上面这段代码会打印出:

type: float64
kind is float64: true
value: 3.4
</div>

还有一些用来修改数据的方法,比如SetInt、SetFloat,在讨论它们之前,我们要先理解“可修改性”(settability),这一特性会在“反射第三定律”中进行详细说明。

反射库提供了很多值得列出来单独讨论的属性。首先是介绍下Value 的 getter 和 setter 方法。为了保证API 的精简,这两个方法操作的是某一组类型范围最大的那个。比如,处理任何含符号整型数,都使用 int64。也就是说 Value 类型的Int 方法返回值为 int64类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型:

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())  
  


 

您可能想查找下面的文章:

  • 谈谈Go语言的反射三定律

相关文章

  • Go语言中的Slice学习总结
  • Go语言实现简单Web服务器的方法
  • go语言环境搭建简述
  • go语言使用scp的方法实例分析
  • go语言变量定义用法实例
  • Go语言之自定义集合Set
  • Go语言struct类型详解
  • go语言if/else语句简单用法示例
  • Go语言基本的语法和内置数据类型初探
  • Go语言操作redis用法实例

文章分类

  • vbs
  • DOS/BAT
  • hta/htc
  • python
  • perl
  • VBA
  • ColdFusion
  • ruby
  • PowerShell
  • Lua
  • Golang
  • linux shell

最近更新的内容

    • Win7环境下搭建Go开发环境(基于VSCode编辑器)
    • Go语言里的结构体文法实例分析
    • go语言实现一个最简单的http文件服务器实例
    • 深入分析golang多值返回以及闭包的实现
    • Go语言中使用gorm小结
    • go语言中的interface使用实例
    • Go语言实现类似c++中的多态功能实例
    • 使用Go语言简单模拟Python的生成器
    • Go语言结构体定义和使用方法
    • golang模板template自定义函数用法示例

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有