第06天-进阶-反射详解

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\n", 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\n", 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\n", 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\n", a)
13 }
程序崩溃了

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

扫描二维码关注公众号,回复: 5221236 查看本文章

解决方法:传地址!

 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\n", 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\n", i, val.Elem().Field(i).Kind())
45     }
46 
47     fmt.Printf("struct has %d fields\n", num)
48 
49     tag := tye.Elem().Field(0).Tag.Get("json")
50     fmt.Printf("tag=%s\n", tag)  //tag=student_name
51 
52     numOfMethod := val.Elem().NumMethod()  //2
53     fmt.Printf("struct has %d methods\n", 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\n", 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\n", 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\n", 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,具有以下功能:
     a. 增加用户登录、注册功能
     b. 增加借书过期的图书界面
     c. 增加显示热门图书的功能,被借次数最多的top10
     d. 增加查看某个人的借书记录的功能

参考文献:

https://studygolang.com/articles/13178 (Go 系列教程 - 反射)
https://golang.org/pkg/reflect/
https://www.cnblogs.com/52php/p/6337420.html

猜你喜欢

转载自www.cnblogs.com/xuejiale/p/10398405.html