原文地址html
第一次翻译文章,请各路人士多多指教!golang
由于映射建设在类型的基础之上,首先咱们对类型进行全新的介绍。
go是一个静态性语言,每一个变量都有静态的类型,所以每一个变量在编译阶段中有明确的变量类型,好比像:int、float3二、MyType。。。函数
好比:ui
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中他们是静态定义的,一个接口类型的变量老是有着相同类型的类型,尽管在运行过程当中存储在接口类型变量的值具备不一样的类型,可是接口类型的变量永远是静态的类型。code
关于go中接口类型的表示方法Russ Cox大神在一篇博客中已经详细介绍[blog:http://research.swtch.com/2009/12/go-data-structures-interfaces.html]
一个接口类型的变量存储一对信息:具体值,值的类型描述。更具体一点是,值是实现接口的底层具体数据项,类型是数据项类型的完整描述。htm
举个例子:对象
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)却用于其完整的类型信息,所以咱们能够按照以下方法调用blog
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),也就是接口类型不能接受接口类型的变量。
在最底层,映射是对存储在接口内部数据对(值、类型)的解释机制。首先咱们须要知道在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之类的方法,可是咱们必须谨慎的使用它们。
反射机制有两个重要的性质。首先,为了保证接口的简洁行,getter
和setter
两个方法是能够接受最大类型值的赋值,好比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中的映射也有其自身的相反性。
经过利用Interface
的方法咱们能够将interface.Value
恢复至接口类型,实际上这个方法将type和value信息包装至interface类型而且返回该值。
// Interface returns v's value as an interface{}. func (v Value) Interface() interface{}
所以咱们能够说
y := v.Interface().(float64) // y will have type float64. fmt.Println(y)
打印float64类型的值,实际上是接口类型变量v的映射。
或者咱们能够这样作,fmt.Println
, fmt.Printf
等函数的参数尽管是空的接口类型也能运行,在fmt包里面解析出type和value的方法和咱们上面的例子类似。所以全部正确打印reflect.Value
的方法都试经过interface的方法将值传递给格式化打印函数。
fmt.Println(v.Interface())
(为何不是fmt.Println(v)
?由于经过v是reflect.Value类型.)由于咱们的值底层是float64类型,所以咱们甚至能够浮点类型的格式打印.
fmt.Printf("value is %7.1e\n", v.Interface())
结果是
3.4e+00
所以咱们不用类型断言v.Interface{}到float64类型。由于接口类型内部保存着值的信息,Printf函数可以恢复这些信息。
简单的说Interface是ValueOf的反操做,除非这个值老是静态的Interface类型。
第三法则比较微妙而且容易混淆,可是若是从第一准则开始看的话,那么仍是比较容易理解的。
这是一条错误的语句,可是这个错误值得咱们研究
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic.
若是你运行这条语句则会有下面的报错信息
panic: reflect.Value.SetFloat using unaddressable value
由于变量v是不可更改的,因此提示值7.1是不可寻址的。可赋值是value的一个特性,可是并非因此的value都具备这个特性。
CanSet
方法返回该值是不是能够改变的,好比
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet())
结果是
settability of v: false
若是在不能够赋值的变量上进行赋值,就回引发错误。可是究竟是什么才是能够赋值的呢?
可赋值的有点像是可寻址的,可是会更严格。映射对象能够更改存储值的特性能够用来建立新的映射对象。映射对象包含原始的数据项是决定映射对象可赋值的关键。当下面代码运行时
var x float64 = 3.4 v := reflect.ValueOf(x)
只是将x的拷贝到reflect.ValueOf
,所以reflect.ValueOf
的返回值是x的复制项,而不是x自己。假以下面这条语句能够正常运行
v.SetFloat(5.4)
尽管v看起来是由x建立的,可是并不会更新x的值,由于这条语句会更新x拷贝值的值,可是并不影响x自己,所以可更改的这一特性就是为了不这种操做。
虽然这看起来很古怪,但其实这是一种很熟悉的操做。好比咱们将x值赋值给一个方法
f(x)
咱们自己不想修改x的值,由于传入的只是x值的拷贝,可是若是咱们想修改x的值,那么咱们须要传送x的地址(也就是x的指针)
f(&x)
这种操做是简单明了的,其实对于映射也是同样的。若是咱们想经过映射修改x的值,那么咱们须要传送x的指针。好比
var x float64 = 3.4 p := reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of p:", p.Type()) fmt.Println("settability of p:", p.CanSet())
结果
type of p: *float64 settability of p: false
映射对象p仍然是不可修改的,可是其实咱们并不想修改p,而是*p。为了获得指针的指向,咱们须要使用Elem()
方法,该方法将会指向*p的值,而且将其保存到映射变量中
v := p.Elem() fmt.Println("settability of v:", v.CanSet())
结果为
settability of v: true
如今v是一个可修改的映射对象。而且v表明x,所以咱们可使用v.SetFloat()
来修改x的值。
v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)
输出结果为
7.1 7.1
映射是比较难理解的,尽管咱们经过映射的Values``Types
隐藏了到底发生了什么操做。咱们只须要记住若是想改变它的值,那在调用ValuesOf
方法时应该使用指向它的指针。
在上一个例子中v并非指向自身的指针,而是经过其余方式产生的。还有一种经常使用的操做就是修改结构体的某个字段,只要咱们知道告终构体的地址,咱们就能修改它的字段。
这有一个修改结构体变量t的例子。由于咱们要修改结构体的字段,因此咱们使用结构体指针建立结构体对象。咱们使用typeOfT表明t的数据类型,并经过NumField方法迭代结构体的字段。主意:咱们只是提取出结构体类型字段的的名字,而他们的reflect.Value
对象。
type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) }
输出结果是
0: A int = 23 1: B string = skidoo
值得注意的是只有可导出的字段才能使可修改的。
由于s包含一个可修改的映射对象,因此咱们能够修改结构体的字段
s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)
结果为
t is now {77 Sunset Strip}
若是s是经过t建立而不是&t,那么SetInt和SetString方法都会出错,由于t的字段是不能够修改的。