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{}类型

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

 package main

 import (
"fmt"
"reflect"
) type Student struct {
Name string
Age int
Score float32
} func test(b interface{}) {
t := reflect.TypeOf(b)
fmt.Println("t: ", t) //t: main.Student v := reflect.ValueOf(b)
fmt.Println("v: ", v) //v: {stu01 18 92} k := v.Kind()
fmt.Println("k: ", k) //k: struct iv := v.Interface()
fmt.Println("iv: ", iv) //iv: {stu01 18 92}
stu, ok := iv.(Student)
if ok {
fmt.Printf("%v %T\n", stu, stu) //{stu01 18 92} main.Student
}
} func main() {
var a Student = Student{
Name: "stu01",
Age: ,
Score: ,
}
test(a)
}

example

 package main

 import (
"fmt"
"reflect"
) func main() { var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) //type: float64
v := reflect.ValueOf(x)
fmt.Printf("value:%v, type:%T\n", v, v) //value:3.4, type: reflect.Valuetype
fmt.Println("type:", v.Type()) //type: float64
fmt.Println("kind:", v.Kind()) //kind: float64
fmt.Println("value:", v.Float()) //value: 3.4 fmt.Println(v.Interface()) //3.4
fmt.Printf("value is %5.2e\n", v.Interface()) //value is 3.40e+00
y := v.Interface().(float64) //v -> Interface -> float64 -> y
fmt.Println(y) //3.4
}

example2

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

 const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)

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)

 package main

 import (
"fmt"
"reflect"
) func main() {
var a float64
fv := reflect.ValueOf(a)
fv.SetFloat(3.3)
fmt.Printf("%v\n", a)
}

程序崩溃了

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

解决方法:传地址!

 package main

 import (
"fmt"
"reflect"
) func main() {
var a float64 = 1.0 fv := reflect.ValueOf(&a)
fv.Elem().SetFloat(3.3) //相当于var *fv float64; *fv = 3.3
fmt.Printf("%v\n", a) //3.3
}

传地址

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

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

 func (Value) Call
func (v Value) Call(in []Value) []Value
Call calls the function v with the input arguments in. For example, if len(in) == , v.Call(in) represents the Go call v(in[], in[], in[]). 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. func (Value) Elem
func (v Value) Elem() Value
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. func (Value) NumField
func (v Value) NumField() int
NumField returns the number of fields in the struct v. It panics if v's Kind is not Struct.

Elem, NumField,Call官方解释

 package main

 import (
"encoding/json"
"fmt"
"reflect"
) type Student struct {
Name string `json:"student_name"`
Age int
Score float32
Sex string
} func (s Student) Print() {
fmt.Println("---start----")
fmt.Println(s)
fmt.Println("---end----")
} func (s Student) Set(name string, age int, score float32, sex string) {
s.Name = name
s.Age = age
s.Score = score
s.Sex = sex
fmt.Println("set -----------")
} func TestStruct(a interface{}) {
tye := reflect.TypeOf(a)
val := reflect.ValueOf(a)
kd := val.Kind()
// fmt.Println(tye, val, kd, val.Elem().Kind()) //*main.Student &{stu01 18 92.8 } ptr struct if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
fmt.Println("expect struct")
return
} num := val.Elem().NumField() //获取val
val.Elem().Field().SetString("stu1000")
for i := ; i < num; i++ {
fmt.Printf("%d %v\n", i, val.Elem().Field(i).Kind())
} fmt.Printf("struct has %d fields\n", num) tag := tye.Elem().Field().Tag.Get("json")
fmt.Printf("tag=%s\n", tag) //tag=student_name numOfMethod := val.Elem().NumMethod() //
fmt.Printf("struct has %d methods\n", numOfMethod)
var params []reflect.Value
val.Elem().Method().Call(params)
} func main() {
var a Student = Student{
Name: "stu01",
Age: ,
Score: 92.8,
} result, _ := json.Marshal(a)
fmt.Println("json result:", string(result)) TestStruct(&a)
fmt.Println(a)
}

example

6. 反射中调用函数

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

 package main

 import "fmt"

 func Print() {
fmt.Println("Hello world!")
} func main() {
f := Print
f()
}

函数作为变量

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

 package main

 import (
"fmt"
"reflect"
) func Print() {
fmt.Println("Hello world!")
} func main() {
f := Print
fv := reflect.ValueOf(f)
fmt.Println("fv is reflect.Func? ", fv.Kind() == reflect.Func) //fv is reflect.Func? true
fv.Call(nil) //Hello world!
}

测试reflect.Func

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

 package main

 import (
"fmt"
"reflect"
) func Print(str string) string {
str = fmt.Sprintf("hello %s", str)
return str
} func main() {
fv := reflect.ValueOf(Print)
params := make([]reflect.Value, ) // 传给Print的参数
params[] = reflect.ValueOf("zhangsan") // 参数设置为"zhangsan"
rs := fv.Call(params) // rs作为结果接受函数的返回值
//result: hello zhangsan
fmt.Println("result:", rs[].Interface().(string)) // rs[0].Interface() ok
}

example

7. 反射中调用方法

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

 package main

 import (
"fmt"
"reflect"
) type Student struct {
Name string
Age int
} func (s *Student) SetName(name string) {
s.Name = name
} func (s *Student) SetAge(age int) {
s.Age = age
} func (s *Student) Print() string {
str := fmt.Sprintf("%s is %d years old.", s.Name, s.Age)
return str
} func main() {
stu := &Student {
Name:"zhangsan",
Age:,
} ref := reflect.ValueOf(stu)
fmt.Println("Before:", ref.MethodByName("Print").Call(nil)[]) //Before: zhangsan is 20 years old. params := make([]reflect.Value, ) params[] = reflect.ValueOf("lisi")
ref.MethodByName("SetName").Call(params)
fmt.Println(stu) //&{lisi 20} params[] = reflect.ValueOf()
ref.MethodByName("SetAge").Call(params) fmt.Println(stu) //&{lisi 22}
fmt.Println("After:", ref.MethodByName("Print").Call(nil)[]) //After: lisi is 22 years old.
}

example

练习1:

 package main

 import (
"fmt"
"reflect"
) type NotknownType struct {
s1 string
s2 string
s3 string
} func (n NotknownType) String() string {
return n.s1 + "-" + n.s2 + "-" + n.s3
} var secret interface{} = NotknownType{"Ada", "Go", "Oberon"} func main() {
value := reflect.ValueOf(secret)
typ := reflect.TypeOf(secret)
fmt.Println(typ) // Main.NotknownType knd := value.Kind()
fmt.Println(knd) // struct for i := ; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i))
//value.Field(i).SetString("C#")
} results := value.Method().Call(nil)
fmt.Println(results) // [Ada - Go - Oberon]
}

练习

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

 package main

 import (
"fmt"
"reflect"
) type NotknownType struct {
s1 string
s2 string
s3 string
} func (n NotknownType) String() string {
return n.s1 + "-" + n.s2 + "-" + n.s3
} var secret interface{} = NotknownType{"Ada", "Go", "Oberon"} func main() {
value := reflect.ValueOf(secret)
typ := reflect.TypeOf(secret)
fmt.Println(typ) // Main.NotknownType knd := value.Kind()
fmt.Println(knd) // struct for i := ; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i))
//value.Field(i).SetString("C#")
} results := value.Method().Call(nil)
fmt.Println(results) // [Ada - Go - Oberon]
}

example

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

 package main

 import (
"fmt"
"reflect"
) type T struct {
A int
B string
} func main() {
t := T{, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type() //main.T
for i := ; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
s.Field().SetInt()
s.Field().SetString("Sunset Strip")
fmt.Println("t is now", t) //t is now {77 Sunset Strip}
}

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

Go语言学习之6 反射详解的更多相关文章

  1. Go语言学习之8 goroutine详解、定时器与单元测试

    主要内容: 1.Goroutine2. Chanel3. 单元测试 1. Goroutine Go 协程(Goroutine)(轻量级的线程,开线程没有数量限制).   (1)进程和线程 A. 进程是 ...

  2. [深入学习Web安全](5)详解MySQL注射

    [深入学习Web安全](5)详解MySQL注射 0x00 目录 0x00 目录 0x01 MySQL注射的简单介绍 0x02 对于information_schema库的研究 0x03 注射第一步—— ...

  3. Shell学习之Bash变量详解(二)

    Shell学习之Bash变量详解 目录 Bash变量 Bash变量注意点 用户自定义变量 环境变量 位置参数变量 预定义变量 Bash变量 用户自定义变量:在Bash中由用户定义的变量. 环境变量:这 ...

  4. Asp.Net MVC学习总结之过滤器详解(转载)

    来源:http://www.php.cn/csharp-article-359736.html   一.过滤器简介 1.1.理解什么是过滤器 1.过滤器(Filters)就是向请求处理管道中注入额外的 ...

  5. C#反射の反射详解

    C#反射の反射详解(点击跳转)C#反射の反射接口(点击跳转)C#反射反射泛型接口(点击跳转)C#反射の一个泛型反射实现的网络请求框架(点击跳转) 一.什么是反射 反射(Reflection):这是.N ...

  6. Linux学习之用户配置文件详解(十四)

    Linux学习之用户配置文件详解 目录 用户信息文件/etc/password 影子文件/etc/shadow 组信息文件/etc/group 组密码文件/etc/gshadow 用户信息文件/etc ...

  7. [转载]springmvc学习之@ModelAttribute运用详解

    spring学习之@ModelAttribute运用详解 链接

  8. expect学习笔记及实例详解【转】

    1. expect是基于tcl演变而来的,所以很多语法和tcl类似,基本的语法如下所示:1.1 首行加上/usr/bin/expect1.2 spawn: 后面加上需要执行的shell命令,比如说sp ...

  9. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

随机推荐

  1. oracle_sqlplus命令行乱码问题解决

    在linux以及unix中,sqlplus的上下左右.回退无法使用,会出现乱码情况. 而rlwrap这个软件就是用来解决这个的. 首先下载rlwrap包:https://linux.linuxidc. ...

  2. Ant build.xml详解

    Ant的概念 可能有些读者并不连接什么是Ant以及入可使用它,但只要使用通过Linux系统得读者,应该知道make这个命令.当编译Linux内核及一些软件的源程序时,经常要用这个命令.Make命令其实 ...

  3. FireMonkey 源码学习(6)

    (6)GetGlyph和GetBaseline TFontGlyphManager是一个抽象类,在不同平台上的实现是不同的,以Windows为例,在FMX.FontGlyphs.Win.pas文件中定 ...

  4. 如何在gvim中安装autoproto自动显示函数原型

    cankao: http://www.vim.org/scripts/script.php?script_id=1553 注意, 在gvim中执行的命令, :foo和:!foo 的区别, 跟vim一样 ...

  5. POJ 3041 Asteroids(最小点覆盖)题解

    题意:n*n的网格中有k个点,开一枪能摧毁一行或一列的所有点,问最少开几枪 思路:我们把网格看成两个集合,行集合和列集合,如果有点x,y那么就连接x->y,所以我们只要做最小点覆盖就好了. 参考 ...

  6. promise对象的使用

    ES6中的promise的出现给我们很好的解决了回调地狱的问题,在使用ES5的时候,在多层嵌套回调时,写完的代码层次过多,很难进行维护和二次开发,ES6认识到了这点问题, 现在promise的使用,完 ...

  7. HDU 2612 Find a way(找条路)

    HDU 2612 Find a way(找条路) 00 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)   Problem  ...

  8. [分享]Active-HDL 9.2 安装

    Download 点击下载Active-HDL 9.2 How to Install ? 解压后依次进行以下操作 1.运行Active_HDL_9.2sp1_main_setup.exe,允许程序所有 ...

  9. windows 上让文件类型和程序关联的批处理程序。

    文件关联工具 地址: https://github.com/wll8/assoc-tool 本工具可以用来为你的便携程序添加文件关联,比如 nodepad2.exe . vscode 或其他图片处理程 ...

  10. POJ 3415 Common Substrings(长度不小于K的公共子串的个数+后缀数组+height数组分组思想+单调栈)

    http://poj.org/problem?id=3415 题意:求长度不小于K的公共子串的个数. 思路:好题!!!拉丁字母让我Wa了好久!!单调栈又让我理解了好久!!太弱啊!! 最简单的就是暴力枚 ...