• Go语言学习之6 反射详解


    1.反射:    

           定义: 反射就是程序能够在运行时检查变量和值,求出它们的类型。
                       可以在运行时动态获取变量的相关信息
                       Import ("reflect")

           为什么需要反射?
           想象下:如果程序中每个变量都是我们自己定义的,那么在编译时就可以知道变量类型了,但是实际中并非如此,就需要我们在运行时检查变量,求出它的类型。这就需要用到反射。

           在 Go 语言中,reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。

    几个函数:
    a. reflect.TypeOf(val),获取变量的类型,返回reflect.Type类型
    b. reflect.ValueOf(val),获取变量的值,返回reflect.Value类型
    c. reflect.Value.Kind(),获取变量的类别,返回一个常量
    d. reflect.Value.Interface(),转换成interface{}类型

           变量接口及获取变量值之间的转换:

           

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type Student struct {
     9     Name  string
    10     Age   int
    11     Score float32
    12 }
    13 
    14 func test(b interface{}) {
    15     t := reflect.TypeOf(b)
    16     fmt.Println("t: ", t)  //t:  main.Student
    17 
    18     v := reflect.ValueOf(b)  
    19     fmt.Println("v: ", v)  //v:  {stu01 18 92}
    20 
    21     k := v.Kind()
    22     fmt.Println("k: ", k)  //k:  struct
    23 
    24     iv := v.Interface()
    25     fmt.Println("iv: ", iv)  //iv:  {stu01 18 92}
    26     stu, ok := iv.(Student)
    27     if ok {
    28         fmt.Printf("%v %T
    ", stu, stu)  //{stu01 18 92} main.Student
    29     }
    30 }
    31 
    32 func main() {
    33     var a Student = Student{
    34         Name:  "stu01",
    35         Age:   18,
    36         Score: 92,
    37     }
    38     test(a)
    39 }
    example
     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func main() {
     9 
    10     var x float64 = 3.4
    11     fmt.Println("type:", reflect.TypeOf(x))  //type: float64
    12     v := reflect.ValueOf(x)
    13     fmt.Printf("value:%v, type:%T
    ", v, v)  //value:3.4, type: reflect.Valuetype 
    14     fmt.Println("type:", v.Type())  //type: float64
    15     fmt.Println("kind:", v.Kind())  //kind: float64
    16     fmt.Println("value:", v.Float())  //value: 3.4
    17 
    18     fmt.Println(v.Interface())  //3.4
    19     fmt.Printf("value is %5.2e
    ", v.Interface())  //value is 3.40e+00
    20     y := v.Interface().(float64)  //v -> Interface -> float64 -> y
    21     fmt.Println(y)  //3.4
    22 }
    example2

    2. reflect.Value.Kind()方法返回的常量

     1 const (
     2     Invalid Kind = iota
     3     Bool
     4     Int
     5     Int8
     6     Int16
     7     Int32
     8     Int64
     9     Uint
    10     Uint8
    11     Uint16
    12     Uint32
    13     Uint64
    14     Uintptr
    15     Float32
    16     Float64
    17     Complex64
    18     Complex128
    19     Array
    20     Chan
    21     Func
    22     Interface
    23     Map
    24     Ptr
    25     Slice
    26     String
    27     Struct
    28     UnsafePointer
    29 ) 
    Kind返回的常量

    3. 获取变量的值:

    reflect.ValueOf(x).Float() 
    reflect.ValueOf(x).Int()
    reflect.ValueOf(x).String()
    reflect.ValueOf(x).Bool()

    4. 通过反射的来改变变量的值
        reflect.Value.SetXX相关方法,比如:

    reflect.Value.SetFloat(),设置浮点数
    reflect.Value.SetInt(),设置整数
    reflect.Value.SetString(),设置字符串

    练习:(panic: reflect: reflect.Value.SetFloat using unaddressable value)

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func main() {
     9     var a float64
    10     fv := reflect.ValueOf(a)
    11     fv.SetFloat(3.3)
    12     fmt.Printf("%v
    ", a)
    13 }
    程序崩溃了

    崩溃的原因:还是值类型和引用类型的原因。v := reflect.ValueOf(x) ,v是x的一个拷贝,修改v,x不会修改!

    解决方法:传地址!

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func main() {
     9     var a float64 = 1.0
    10 
    11     fv := reflect.ValueOf(&a)
    12     fv.Elem().SetFloat(3.3)   //相当于var *fv float64;  *fv = 3.3
    13     fmt.Printf("%v
    ", a) //3.3
    14 }
    传地址

    其中fv.Elem().Setxx用来获取指针指向的变量,相当于:
           var a *int;
           *a = 100

    5. 用反射操作结构体
        a. reflect.Value.NumField() 获取结构体中字段的个数
        b. reflect.Value.Method(n).Call 来调用结构体中的方法

     1 func (Value) Call
     2 func (v Value) Call(in []Value) []Value
     3 Call calls the function v with the input arguments in. For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]). Call panics if v's Kind is not Func. It returns the output results as Values. As in Go, each input argument must be assignable to the type of the function's corresponding input parameter. If v is a variadic function, Call creates the variadic slice parameter itself, copying in the corresponding values.
     4 
     5 func (Value) Elem
     6 func (v Value) Elem() Value
     7 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.
     8 
     9 func (Value) NumField
    10 func (v Value) NumField() int
    11 NumField returns the number of fields in the struct v. It panics if v's Kind is not Struct.
    Elem, NumField,Call官方解释
     1 package main
     2 
     3 import (
     4     "encoding/json"
     5     "fmt"
     6     "reflect"
     7 )
     8 
     9 type Student struct {
    10     Name  string `json:"student_name"`
    11     Age   int
    12     Score float32
    13     Sex   string
    14 }
    15 
    16 func (s Student) Print() {
    17     fmt.Println("---start----")
    18     fmt.Println(s)
    19     fmt.Println("---end----")
    20 }
    21 
    22 func (s Student) Set(name string, age int, score float32, sex string) {
    23     s.Name = name
    24     s.Age = age
    25     s.Score = score
    26     s.Sex = sex
    27     fmt.Println("set -----------")
    28 }
    29 
    30 func TestStruct(a interface{}) {
    31     tye := reflect.TypeOf(a)
    32     val := reflect.ValueOf(a)
    33     kd := val.Kind()
    34     // fmt.Println(tye, val, kd, val.Elem().Kind()) //*main.Student &{stu01 18 92.8 } ptr struct
    35 
    36     if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
    37         fmt.Println("expect struct")
    38         return
    39     }
    40 
    41     num := val.Elem().NumField()  //获取val
    42     val.Elem().Field(0).SetString("stu1000")
    43     for i := 0; i < num; i++ {
    44         fmt.Printf("%d %v
    ", i, val.Elem().Field(i).Kind())
    45     }
    46 
    47     fmt.Printf("struct has %d fields
    ", num)
    48 
    49     tag := tye.Elem().Field(0).Tag.Get("json")
    50     fmt.Printf("tag=%s
    ", tag)  //tag=student_name
    51 
    52     numOfMethod := val.Elem().NumMethod()  //2
    53     fmt.Printf("struct has %d methods
    ", numOfMethod)
    54     var params []reflect.Value
    55     val.Elem().Method(0).Call(params)
    56 }
    57 
    58 func main() {
    59     var a Student = Student{
    60         Name:  "stu01",
    61         Age:   18,
    62         Score: 92.8,
    63     }
    64 
    65     result, _ := json.Marshal(a)
    66     fmt.Println("json result:", string(result))
    67 
    68     TestStruct(&a)
    69     fmt.Println(a)
    70 }
    example

    6. 反射中调用函数

    Go中的函数是可以像普通的 int、float 等类型变量那样作为值的。例如:

     1 package main
     2  
     3 import "fmt"
     4  
     5 func Print() {
     6     fmt.Println("Hello world!")
     7 }
     8  
     9 func main() {
    10     f := Print
    11     f()
    12 }
    函数作为变量

    和函数作为变量类似,在反射中函数和方法的类型(Type)都是 reflect.Func,如果要调用函数的话,可以通过 Value的Call()方法。

     1 package main
     2  
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func Print() {
     9     fmt.Println("Hello world!")
    10 }
    11  
    12 func main() {
    13     f := Print
    14     fv := reflect.ValueOf(f)
    15     fmt.Println("fv is reflect.Func? ", fv.Kind() == reflect.Func)  //fv is reflect.Func?  true
    16     fv.Call(nil)  //Hello world!
    17 }
    测试reflect.Func

    Call函数的定义:func (v Value) Call(in []Value) []Value
    Value 的 Call() 方法的参数是一个 Value 的 slice,对应的反射函数类型的参数,返回值也是一个 Value 的 slice,同样对应反射函数类型的返回值。

     1 package main
     2  
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func Print(str string) string {
     9     str = fmt.Sprintf("hello %s", str)
    10     return str
    11 }
    12  
    13 func main() {
    14     fv := reflect.ValueOf(Print)
    15     params := make([]reflect.Value, 1)                 // 传给Print的参数
    16     params[0] = reflect.ValueOf("zhangsan")            // 参数设置为"zhangsan"
    17     rs := fv.Call(params)                              // rs作为结果接受函数的返回值
    18     //result: hello zhangsan
    19     fmt.Println("result:", rs[0].Interface().(string)) // rs[0].Interface() ok
    20 }
    example

    7. 反射中调用方法

    函数和方法可以说其实本质上是相同的,只不过方法与一个“对象”进行了“绑定”,方法是“对象”的一种行为,这种行为是对于这个“对象”的一系列操作,例如修改“对象”的某个属性。

     1 package main
     2  
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type Student struct {
     9     Name string
    10     Age int
    11 }
    12 
    13 func (s *Student) SetName(name string) {
    14     s.Name = name
    15 }
    16 
    17 func (s *Student) SetAge(age int) {
    18     s.Age = age
    19 }
    20 
    21 func (s *Student) Print() string {
    22     str := fmt.Sprintf("%s is %d years old.", s.Name, s.Age)
    23     return str
    24 }
    25  
    26 func main() {
    27     stu := &Student {
    28         Name:"zhangsan",
    29         Age:20,
    30     }
    31 
    32     ref := reflect.ValueOf(stu) 
    33     fmt.Println("Before:", ref.MethodByName("Print").Call(nil)[0]) //Before: zhangsan is 20 years old.
    34 
    35     
    36     params := make([]reflect.Value, 1)
    37 
    38     params[0] = reflect.ValueOf("lisi")
    39     ref.MethodByName("SetName").Call(params)
    40     fmt.Println(stu)  //&{lisi 20}
    41 
    42     params[0] = reflect.ValueOf(22)
    43     ref.MethodByName("SetAge").Call(params)
    44 
    45     fmt.Println(stu)  //&{lisi 22}
    46     fmt.Println("After:", ref.MethodByName("Print").Call(nil)[0])  //After: lisi is 22 years old.
    47 }
    example

    练习1:

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type NotknownType struct {
     9     s1 string
    10     s2 string
    11     s3 string
    12 }
    13 
    14 func (n NotknownType) String() string {
    15     return n.s1 + "-" + n.s2 + "-" + n.s3
    16 }
    17 
    18 var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
    19 
    20 func main() {
    21     value := reflect.ValueOf(secret) 
    22     typ := reflect.TypeOf(secret)    
    23     fmt.Println(typ)  // Main.NotknownType
    24 
    25     knd := value.Kind()
    26     fmt.Println(knd) // struct
    27     
    28     for i := 0; i < value.NumField(); i++ {
    29         fmt.Printf("Field %d: %v
    ", i, value.Field(i))
    30         //value.Field(i).SetString("C#")
    31     }
    32     
    33     results := value.Method(0).Call(nil)
    34     fmt.Println(results) // [Ada - Go - Oberon]
    35 }
    练习

    练习2:通过反射操作结构体

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type NotknownType struct {
     9     s1 string
    10     s2 string
    11     s3 string
    12 }
    13 
    14 func (n NotknownType) String() string {
    15     return n.s1 + "-" + n.s2 + "-" + n.s3
    16 }
    17 
    18 var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
    19 
    20 func main() {
    21     value := reflect.ValueOf(secret) 
    22     typ := reflect.TypeOf(secret)    
    23     fmt.Println(typ)  // Main.NotknownType
    24 
    25     knd := value.Kind()
    26     fmt.Println(knd) // struct
    27     
    28     for i := 0; i < value.NumField(); i++ {
    29         fmt.Printf("Field %d: %v
    ", i, value.Field(i))
    30         //value.Field(i).SetString("C#")
    31     }
    32     
    33     results := value.Method(0).Call(nil)
    34     fmt.Println(results) // [Ada - Go - Oberon]
    35 }
    example

    练习3:通过反射修改结构体

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type T struct {
     9     A int
    10     B string
    11 }
    12 
    13 func main() {
    14     t := T{23, "skidoo"}
    15     s := reflect.ValueOf(&t).Elem()
    16     typeOfT := s.Type() //main.T
    17     for i := 0; i < s.NumField(); i++ {
    18         f := s.Field(i)
    19         fmt.Printf("%d: %s %s = %v
    ", i,
    20             typeOfT.Field(i).Name, f.Type(), f.Interface())
    21     }
    22     s.Field(0).SetInt(77)
    23     s.Field(1).SetString("Sunset Strip")
    24     fmt.Println("t is now", t) //t is now {77 Sunset Strip}
    25 }
    example

    建议:反射是 Go 语言中非常强大和高级的概念,我们应该小心谨慎地使用它。使用反射编写清晰和可维护的代码是十分困难的。你应该尽可能避免使用它,只在必须用到它时,才使用反射。

    图书管理系统v2版本开发:

         实现一个图书管理系统v2,具有以下功能:
         a. 增加用户登录、注册功能
         b. 增加借书过期的图书界面
         c. 增加显示热门图书的功能,被借次数最多的top10
         d. 增加查看某个人的借书记录的功能

    参考文献:

    • https://studygolang.com/articles/13178 (Go 系列教程 - 反射)
    • https://golang.org/pkg/reflect/
    • https://www.cnblogs.com/52php/p/6337420.html
  • 相关阅读:
    Date计算人活了多少天
    微信红包平均分法
    math practise
    Array sort
    static memory management
    java数组中的选择排序
    java数组中的冒泡排序
    数组联系2 模拟酒店系统
    数组练习1(模拟栈)
    二维数组
  • 原文地址:https://www.cnblogs.com/xuejiale/p/10398405.html
Copyright © 2020-2023  润新知