来自 电脑系统 2019-09-21 04:06 的文章
当前位置: 金沙澳门官网网址 > 电脑系统 > 正文

Go基础系列,三个特性

接口类型探测:类型断言

接口实例中存储了实现接口的类型实例,类型的实例有两种:值类型实例和指针类型实例。在程序运行过程中,接口实例存储的实例类型可能会动态改变。例如:

// ins是接口实例var ins Shaper// ins存储值类型的实例ins = c1// 一段时间后......// ins存储指针类型的实例,存储的类型发生改变ins = c2// 一段时间后...// ins可能存储另一个类型实例ins = s1

所以,需要一种探测接口实例所存储的是值类型还是指针类型。

探测的方法是:ins.ins.。它们有两个返回值,第二个返回值是ok返回值,布尔类型,第一个返回值是探测出的类型。也可以只有一个返回值:探测出的类型。

// 如果ins保存的是值类型的Type,则输出if t, ok := ins.; ok {    fmt.Printf("%Tn", v)}// 如果ins保存的是指针类型的*Type,则输出if t, ok := ins.; ok {    fmt.Printf("%Tn", v)}// 一个返回值的探测t := ins.t := ins.

以下是一个例子:

package mainimport "fmt"// Shaper 接口类型type Shaper interface {    Area() float64}// Square struct类型type Square struct {    length float64}// Square类型实现Shaper中的方法Area()func  Area() float64 {    return s.length * s.length}func main() {    var ins1, ins2 Shaper    // 指针类型的实例    s1 := new    s1.length = 3.0    ins1 = s1    if v, ok := ins1.; ok {        fmt.Printf("ins1: %Tn", v)    }    // 值类型的实例    s2 := Square{4.0}    ins2 = s2    if v, ok := ins2.; ok {        fmt.Printf("ins2: %Tn", v)    }}

上面两个Printf都会输出,因为它们的类型判断都返回true。如果将ins2.改为ins2.,第二个Printf将不会输出,因为ins2它保存的是值类型的实例。

以下是输出结果:

ins1: *main.Squareins2: main.Square

特别需要注意的是,ins必须明确是接口实例。例如,以下前两种声明是有效的,第三种推断类型是错误的,因为它可能是接口实例,也可能是类型的实例副本。

var ins Shaper     // 正确ins := Shaper  // 正确ins := s1          // 错误

当ins不能确定是接口实例时,用它来进行测试,例如ins.将会报错:

invalid type assertion:ins. (non-interface type (type of ins) on left)

它说明了左边的ins是非接口类型(non-interface type)。

另一方面,通过接口类型断言(ins.),如果Type是一个接口类型,就可以判断接口实例ins中所保存的类型是否也实现了Type接口。例如:

var r io.Readtty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)if err != nil {    return nil, err}r = ttyvar w io.Writerw = r.(io.Writer)

上面的r是io.Read接口的一个实例变量,它里面保存的是tty和它的类型,即(tty, *os.File),然后断言r的类型,探测它里面的类型*File是否也实现了io.Writer接口,如果实现了,则保存到io.Writer接口的实例变量w中,这样w实例也将保存(tty,*os.File)

由于任意内容都实现了空接口,所以,总是可以把一个接口实例无需通过任何断言地赋值给一个空接口实例:

var empty interface{}empty = w

现在empty也保存了(tty,*os.File)

The Laws of Reflection

原文地址

第一次翻译文章,请各路人士多多指教!

type Switch结构

switch流程控制结构还可以用来探测接口实例保存的类型。这种结构称为type-switch

用法如下:

switch v := ins. {case *Square:    fmt.Printf("Type Square %Tn", v)case *Circle:    fmt.Printf("Type Circle %Tn", v)case nil:    fmt.Println("nil value: nothing to check?")default:    fmt.Printf("Unexpected type %T", v)}

其中ins.中的小写type是固定的词语。

以下是一个使用示例:

package mainimport (    "fmt")// Shaper 接口类型type Shaper interface {    Area() float64}// Circle struct类型type Circle struct {    radius float64}// Circle类型实现Shaper中的方法Area()func (c *Circle) Area() float64 {    return 3.14 * c.radius * c.radius}// Square struct类型type Square struct {    length float64}// Square类型实现Shaper中的方法Area()func  Area() float64 {    return s.length * s.length}func main() {    s1 := &Square{3.3}    whichType    s2 := Square{3.4}    whichType    c1 := new    c1.radius = 2.3    whichType}func whichType {    switch v := n. {    case *Square:        fmt.Printf("Type Square %Tn", v)    case Square:        fmt.Printf("Type Square %Tn", v)    case *Circle:        fmt.Printf("Type Circle %Tn", v)    case nil:        fmt.Println("nil value: nothing to check?")    default:        fmt.Printf("Unexpected type %T", v)    }}

上面的type-switch中,之所以没有加上case Circle,是因为Circle只实现了指针类型的receiver,根据Method Set对接口的实现规则,只有指针类型的Circle示例才算是实现了接口Shaper,所以将值类型的示例case Circle放进type-switch是错误的。

类型和接口

因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍。
go是一个静态性语言,每个变量都有静态的类型,因此每个变量在编译阶段中有明确的变量类型,比如像:int、float32、MyType。。。

比如:

type MyInt int
var i int
var j MyInt

变量i的类型为int,变量j的类型为MyInt,变量i、j具有确定的类型,虽然i、j的潜在类型是一样的,但是在没有转换的情况下他们之间不能相互赋值。
在类型中有重要的一类为接口类型(interface),接口类型为一系列方法的集合。一个接口型变量可以存储接口方法中声明的任何具体的值。像io.Reader和io.Writer是一个很好的例子,这两个接口在io包中定义。

type Reader interface{
    Read(p []byte)(n int, err error)
}

type Writer interface{
    Writer(p []byte)(n int,er error)
}

任何声明为io.Reader或者io.Writer类型的变量都可以使用Read或者Writer 方法。也就意味着io.Reader类型的变量可以赋值任何有Read方法的的变量。

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)

无论变量r被赋值什么类型的值,变量r的类型依旧是io.Reader。go语言是静态类型语言,并且r的类型永远是io.Reader。
金沙澳门官网网址 ,在接口类型中有一个重要的极端接口类型--空接口。
interface{}
他代表一个空的方法集合并且可以被赋值为任何值,因为任何一个变量都有0个或者多个方法。
有一种错误的说法是go的接口类型是动态定义的,其实在go中他们是静态定义的,一个接口类型的变量总是有着相同类型的类型,尽管在运行过程中存储在接口类型变量的值具有不同的类型,但是接口类型的变量永远是静态的类型。

接口的表示方法

关于go中接口类型的表示方法Russ Cox大神在一篇博客中已经详细介绍[blog:]
一个接口类型的变量存储一对信息:具体值,值的类型描述。更具体一点是,值是实现接口的底层具体数据项,类型是数据项类型的完整描述。

举个例子:

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

变量r包含两个数据项:值(tty),类型(os.File)。注意os.File实现的方法不仅仅是Read,即使接口类型仅包含Read方法,但是值(tty)却用于其完整的类型信息,因此我们可以按照如下方法调用

var w io.Writer
w = r.(io.Writer)

这条语句是一个断言语句,断言的意思是变量r中的数据项声明为io.Writer,因为我们可以将r赋值给w。执行完这条语句以后,变量w将和r一样包含值(tty)、类型(*os.File)。即使具体值可能包含很多方法,但是接口的静态类型决定什么方法可以通过接口型变量调用。

同样我们可以

var empty interface{}
empty = w

这个接口型变量同样包含一个数据对(tty,*os.File)。空接口可以接受任何类型的变量,并且包含我们可能用到的关于这个变量的所有信息。在这里我们不需要断言是因为w变量满足于空接口。在上一个从Reader向Writer移动数据的例子中,我们需要类型断言,因为Reader接口中不包含Writer方法
切记接口的数据对中的内容只能来自于(value , concrete type)而不能是(value, interface type),也就是接口类型不能接受接口类型的变量。

1.从接口类型到映射对象

在最底层,映射是对存储在接口内部数据对(值、类型)的解释机制。首先我们需要知道在reflect包中的两种类型Type和Value,这两种类型提供了对接口变量内部内容的访问,同时reflect.TypeOf和reflect.ValueOf两个方法检索接口类型的变量。

首先我们开始TypeOf

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var f float64 = 13.4
    fmt.Println(reflect.TypeOf(f))
    fmt.Println("Hello, playground")
}

结果

float64
Hello, playground

我们可以会感到奇怪这里没有接口呀?因为在程序中我们可以得知f的变量类型应为float32,不应该是什么变量类型。但是我们在golang源码中我们得知,reflect.TypeOf包含一个空接口类型的变量.
func TypeOf(i interface{})Type
当我们在调用reflect.TypeOf方法时,x首先存储在一个空的接口中,然后再作为一个参数传送到reflect.TypeOf方法中,然后该方法解压这个空的接口得到类型信息。
同样reflect.ValueOf方法,得到值。

    var f float64 = 13.4
    fmt.Println(reflect.ValueOf(f))

结果

13.4

reflect.Type和reflec.Value有许多方法让我们检查和修改它们。一个比较重要的方法是Value有一个能够返回reflect.Value的类型的方法Type。另外一个比较重要的是Type和Value都提供一个Kind方法,该方法能够返回存储数据项的字长(Uini,Floatr64,Slice等等)。同样Value方法也提供一些叫做Int、Float的方法让我们修改存储在内部的值。

    var f float64 = 13.44444
    v := reflect.ValueOf(f)
    fmt.Println(v)
    fmt.Println(v.Type())
    fmt.Println(v.Kind())
    fmt.Println(v.Float())

结果

13.444444444444445
float64
float64
13.444444444444445

同时有像SetInt、SetFloat之类的方法,但是我们必须谨慎的使用它们。

反射机制有两个重要的性质。首先,为了保证接口的简洁行,gettersetter两个方法是可以接受最大类型值的赋值,比如int64可以接受任何符号整数。所以值的Int方法会返回一个int64类型的值,SetInt接受int64类型的值,因此它可能转化为所涉及的实际类型。

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())                            // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())                                       // v.Uint returns a uint64.

第二个特性:接口保存了数据项底层类型,而不是静态的类型,如果一个接口包含用户定义的整数类型的值,比如

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

则v的Kind方法调用仍然返回的是reflect.Int,尽管x的静态类型是MyInt。也可以说,Kind`不会像Type`一样将MyInt和int当作两种类型来对待。

本文由金沙澳门官网网址发布于电脑系统,转载请注明出处:Go基础系列,三个特性

关键词: