For-learning-Go-Tutorial

Go语言是谷歌2009发布的第二款开源编程语言

Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

因而一直想的是自己可以根据自己学习和使用Go语言编程的心得,写一本Go的书可以帮助想要学习Go语言的初学者快速入门开发和使用!

反射(reflection)

在运行时反射是程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。它同时也是造成迷惑的来源。反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。

golang实现反射是通过reflect包来实现的, 让原本是静态类型的go具备了很多动态类型语言的特征。reflect包有两个数据类型,一个是Type,一个是Value。它定义了两个重要的类型,Type和Value. Type就是定义的类型的一个数据类型,Value是值的类型, TypeOf和ValueOf是获取Type和Value的方法。一个Type表示一个Go类型.它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型.

接着我们开始我们使用Golang反射,通常在使用到Golang反射的时候会有三种定律:

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

这里的反射类型指的是reflect.Type和reflect.Value.将接口类型变量转换为反射类型变量,是通过reflect包的TypeOf和ValueOf实现的。

下面我们来看看在reflect包中的TypeOf和ValueOf的定义:

TypeOf

  1. #TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.
  2. #TypeOf返回表示i的动态类型的反射Type。如果i是nil接口值,则TypeOf返回nil。
  3. func TypeOf(i interface{}) Type

ValueOf

  1. # ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value
  2. # ValueOf返回一个新的Value,初始化为存储在接口i中的具体值。 ValueOf(nil)返回零值
  3. func ValueOf(i interface{}) Value

然后我们可以使用reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型:

  1. var p int = 10
  2. v1 := reflect.ValueOf(p)//返回Value类型对象,值为10
  3. t1 := reflect.TypeOf(p)//返回Type类型对象,值为int
  4. fmt.Println("v1:",v1)
  5. fmt.Println("t1",t1)
  6. v2 := reflect.ValueOf(&p)//返回Value类型对象,值为&p,变量p的地址
  7. t2 := reflect.TypeOf(&p)//返回Type类型对象,值为*int
  8. fmt.Println("v2:",v2)
  9. fmt.Println("t2:",t2)

运行结果:

  1. v1: 10
  2. t1: int
  3. v2: 0xc4200180b8
  4. t2: *int

其中v1和v2中包含了接口中的实际值,t1和t2中包含了接口中的实际类型.由于reflect.ValueOf和reflect.TypeOf的参数类型都是interface{},空接口类型,而返回值的类型是reflect.Value和reflect.Type,中间的转换由reflect包来实现。所以就实现了接口类型变量到反射类型对象的转换.

  • 反射定律二:反射可以将“反射类型对象”转换为“接口类型变量”,

这里根据一个 reflect.Value类型的变量,我们可以使用Interface方法恢复其接口类型的值。事实上,这个方法会把 type和value信息打包并填充到一个接口变量中,然后返回.

下面我们来看看在reflect包中Value的定义:

  1. func (v Value) Interface() (i interface{})
  2. # Interface returns v's current value as an interface{}. It is equivalent to:
  3. # 接口将v的当前值作为接口{}返回。它相当于:
  4. var i interface{} = (v's underlying value)
  5. # It panics if the Value was obtained by accessing unexported struct fields.
  6. # 如果通过访问未导出的struct字段获得Value,则会发生混乱。

然后我们可以使用Value将反射类型转换为接口类型变量:

  1. var a int = 10
  2. v1 := reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址
  3. t1 := reflect.TypeOf(&a) //返回Type类型对象,值为*int
  4. fmt.Println("v1:",v1)
  5. fmt.Println("t1:",t1)
  6. v2 := v1.Interface() //返回空接口变量
  7. v3 := v2.(*int) //类型断言,断定v1中type=*int
  8. fmt.Printf("%T %v\n", v3, v3)
  9. fmt.Println("v3:",*v3)

运行的结果:

  1. v1: 0xc420082010
  2. t1: *int
  3. *int 0xc420082010
  4. v3: 10

reflect.ValueOf 的逆操作是 reflect.Value.Interface方法.它返回一个 interface{}类型,装载着与reflect.Value相同的具体值,这样我们就可以将“反射类型对象”转换为“接口类型变量.

其实reflect.Value 和 interface{} 都能装载任意的值. 有所不同的是, 一个空的接口隐藏了值内部的表示方式和所有方法, 因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样), 内部值我们没法访问. 相比之下, 一个 Value 则有很多方法来检查其内容, 无论它的具体类型是什么.

  • 反射定律三:如果要修改反射类型对象,其值必须是“addressable” 在上面第一种反射定律将“接口类型变量”转换为“反射类型对象”我们可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值,如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。

那么我们可以通过CanSet函数可以判定反射对象是否可以修改。

  1. # CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. If CanSet returns false, calling Set or any type-specific setter (e.g., SetBool, SetInt) will panic.
  2. # CanSet报告是否可以更改v的值.仅当值可寻址且未通过使用未导出的struct字段获取时,才能更改值。如果CanSet返回false,则调用Set或任何特定于类型的setter(例如,SetBool,SetInt)将会发生混乱。
  3. func (v Value) CanSet() bool

注意:CanSet返回false,值是不可以修改的,如果返回true则是可以修改的.

  1. var p float64 = 3.4
  2. v1 := reflect.ValueOf(&p)
  3. if v1.Kind() == reflect.Ptr && !v1.Elem().CanSet() { //判断是否为指针类型,元素是否可以修改
  4.    fmt.Println("cannot set value")
  5.    return
  6. } else {
  7.    v1 = v1.Elem() //实际取得的对象
  8.    fmt.Println("CanSet return bool:", v1.CanSet())
  9. }
  10. v1.SetFloat(6.1)
  11. fmt.Println("p=",p)

运行结果:

  1. CanSet return bool: true
  2. p= 6.1

从运行结果看值修改成功了,但是这里出现了Elem函数作用是用来获取原始值对应的反射对象.

  1. # Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
  2. # Elem返回接口v包含的值或指针v指向的值。如果v的Kind不是Interface或Ptr,它会发生恐慌。如果v为nil,则返回零值。
  3. func (v Value) Elem() Value

在这里要修改变量p的值,首先就要通过reflect.ValueOf来获取p的值类型,refelct.ValueOf返回的值类型是变量p一份值拷贝,要修改变量p就要传递p的地址,从而返回p的地址对象,才可以进行对p变量值对修改操作。在得到p变量的地址值类型之后先调用Elem()返回地址指针指向的值的Value封装。然后通过Set方法进行修改赋值。

通过反射可以很容易的修改变量的值,我们首先要通过反射拿到这个字段的地址值类型,然后去判断反射返回类型是否为reflect.Ptr指针类型(通过指针才能操作对象地址中的值)同时还要判断这个元素是否可以修改,通过Kind()和Set来修改字段的值,然后就可以拿到我们修改的值了.

因此在反射中使用反射包提供refelct.TypeOf和refelct.ValueOf方法获得接口对象的类型,值和方法等。通过反射修改字段值等时候需要传入地址类型,并且需要检查反射返回值类型是否为refelct.Ptr,检查字段是否CanSet,检查字段是存在,然后通过Kind()来帮助赋值相应对类型值。

应用示例:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type User struct {
  7. Name string `json:"name"`
  8. Gender string `json:"gender"`
  9. Age int `json:"age"`
  10. }
  11. func main() {
  12. types := reflect.TypeOf(&User{}).Elem()
  13. value := reflect.ValueOf(&User{}).Elem()
  14. fmt.Println("values Numfield:",value.NumField())
  15. for i:=0;i<types.NumField();i++{
  16. m := types.Field(i).Tag.Get("json")
  17. fmt.Println(m)
  18. }
  19. }

运行:

  1. values Numfield: 3
  2. name
  3. gender
  4. age