背景
先看官方Doc中Rob Pike给出的关于反射的定义:
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
(在计算机领域,反射是一种让程序——主要是通过类型——理解其自身结构的一种能力。它是元编程的组成之一,同时它也是一大引人困惑的难题。)
网上关于Golang反射的教程、博客,早已汗牛充栋。可遍数之下,有些偏于底层实现,有些偏于单一问题,有些偏于性能,有些是官方Doc的翻译,而且质量参差不齐。
在这种情况下,对于想学反射编程的人来说,很难看到一个完整的、系统的关于Golang反射编程的教程,甚至于在某段时间下好像学会了Golang反射,过段时间又忘了,好像从没学过一样。
本文的出现就是为了解决上述问题,争取对Golang反射编程,做一个尽量完善、系统的阐述。本文主要关注点在于:如何学会并使用Golang反射。因此不会讲述太多底层实现和性能方面的问题。
为了便于理解、记忆,本文在阐述过程中,会通过对比Golang一般语法和反射“语法”的方式,来说明如何使用反射编程。
最后提醒一句:有限制地用反射,即便你用的非常6。
基础
上面已经说到,反射主要通过类型,来理解其自身结构,所以反射中,比较关键的一点在于:类型。
另外,编程中所有的操作,都是对数据的操作,亦即值,所以反射中,另外一点在于:值。
在反射编程时,一定要时刻注意,你在操作变量的类型是什么,要对值做什么操作。
Golang反射中,类型的起点是reflect.TypeOf(x),值的起点是reflect.ValueOf(x)。
可读与可写
先看一个简单的例子(后面例子会省略package和import部分):
package main
import "reflect"
func main() {
var i int = 5
v := reflect.ValueOf(i)
println(v.Int()) // Output: 5
}
一定要注意一点,reflect.ValueOf(i)
是个函数调用,所以对比看一下下面这个没有反射的例子:
func echo(v int) {
println(v)
}
func main() {
var i int = 5
echo(i)
}
这两个例子是等价的,在上面反射中,reflect.ValueOf(i)
是函数调用,函数调用分为传值和传址,该写法明显是在传值,它是i
的一份拷贝,就像在调用echo(i)
的时候,给echo
传的是i
的值,即在echo
内部,i
的值是只读的。
所以在下面例子中:
func echo(v int) {
v = 100
println(v) // Output: 100
}
func main() {
var i int = 5
echo(i)
println(i) // Output: 5
}
在echo
中,将v
的值改成了100
,却改不了main
中i
的值,因为调用echo
的本质是传值。传值的意思即只读,因此如果在反射中,对只读的值做任何Set
操作,都会panic
:
func main() {
var i int = 5
v := reflect.ValueOf(i)
v.SetInt(100) // panic
}
那如何改变值呢?很明显,应该传址:
func change(v *int) {
*v = 100
}
func main() {
var i int = 5
change(&i)
println(i) // Output: 100
}
注意在该程序中:
- 为了在函数
change
中改变i
的值,我们将i
的内存地址传给了change
; - 在改变
i
的值时,change
先给v
做了解指针操作*v
。
所以,对应的,反射也是函数调用,写出来,和上面逻辑上应该是一样的:
func main() {
var i int = 5
v := reflect.ValueOf(&i)
v.Elem().SetInt(100)
println(i) // Output: 100
}
Golang反射简易之处在于:只要你学会了该语言,你就学会了反射。
比较一下上面两个程序,有什么不同吗?
v := reflect.ValueOf(&i)
和var v *int = &i
是一样的;v.Elem().SetInt(100)
和*v = 100
是一样的,先解指针,再赋值。
那有没有方法能判断是否可写呢?reflect.Value.CanSet() bool
该函数可以判断一个值是否可写:
func main() {
var i int = 5
v := reflect.ValueOf(i)
println(v.CanSet()) // Output: false
v = reflect.ValueOf(&i)
println(v.CanSet()) // Output: false // 标记1
println(v.Elem().CanSet()) // Output: true
}
标记1
处为什么是false
呢?因为你要改的是指针指向的值,不是指针本身的值。
那具体有哪些是CanSet
的呢?文档:
A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.
注意文档中的一句:未被导出的struct fields
是不可被Set
的。
取址
通过上面例子,我们知道,想要改变值,其前提是:取址。那什么样的变量是可取址的呢?反射包里有一个函数:reflect.Value.CanAddr() bool
,该函数可以判断是否可取址。
func main() {
var i int = 5
v := reflect.ValueOf(i)
println(v.CanAddr()) // Output: false
v = reflect.ValueOf(&i)
println(v.CanAddr()) // Output: false
println(v.Elem().CanAddr()) // Output: true
}
在该函数的文档中,已经明确了哪些可以被取址:
A value is addressable if it is an element of a slice, an element of an addressable array, a field of an addressable struct, or the result of dereferencing a pointer.
为方便理解,我将上述文档翻译成代码:
func main() {
ls := []int{1}
println(reflect.ValueOf(ls).Index(0).CanAddr()) // Output: true
println(reflect.ValueOf(&ls).Elem().Index(0).CanAddr()) // Output: true
var t struct{ a, B int }
println(reflect.ValueOf(&t).Elem().Field(0).CanAddr()) // Output: true
println(reflect.ValueOf(&t).Elem().Field(0).CanSet()) // Output: false
println(reflect.ValueOf(&t).Elem().Field(1).CanAddr()) // Output: true
println(reflect.ValueOf(&t).Elem().Field(1).CanSet()) // Output: true
var p *int
println(reflect.ValueOf(p).Elem().CanSet()) // Output: false
p = &ls[0]
println(reflect.ValueOf(p).Elem().CanSet()) // Output: true
}
既然知道了哪些可以取址,那么也就知道了哪些不可被取址,比如map[k]v
中的k
和v
都不可被取址,没有传址进来的变量都不可被取址……
一定要注意:可取址CanAddr
和可赋值CanSet
不完全等价,主要区别在于未被导出的struct fields
。
对指针和结构体相关的比较好理解,关键问题在于:为什么an element of a slice
是可被取址和赋值的呢?
func change(ls []int) {
ls[0] = 100
}
func main() {
ls := []int{5}
change(ls)
println(ls[0]) // Output: 100
}
在Golang里面,slice
、map
、chan
默认是传址的!
请看:
func main() {
ls := []int{0}
reflect.ValueOf(ls).Index(0).SetInt(123)
println(ls[0]) // Output: 123
m := make(map[string]int)
reflect.ValueOf(m).SetMapIndex(reflect.ValueOf("abc"), reflect.ValueOf(456))
println(m["abc"]) // Output: 456
ch := make(chan int, 1)
reflect.ValueOf(ch).Send(reflect.ValueOf(789))
println(<-ch) // Output: 789
}
注意slice
、map
、chan
默认传址的前提是:已经提前分配好内存空间!
细节知识点:array默认是传值。
func main() {
arr := [...]int{1}
println(reflect.ValueOf(arr).Index(0).CanAddr()) // Output: false
println(reflect.ValueOf(arr).Index(0).CanSet()) // Output: false
}
类型
到目前为止,还没接触到类型。获取数据的类型可以用reflect.ValueOf(x).Kind()
获取真实类型。所以有:
func main() {
var i int
println(reflect.ValueOf(i).Kind().String()) // Output: int
var s string
println(reflect.ValueOf(s).Kind().String()) // Output: string
var iface interface{}
println(reflect.ValueOf(iface).Kind().String()) // Output: invalid // 标记1
println(reflect.ValueOf(&iface).Elem().Kind().String()) // Output: interface // 标记2
println(reflect.ValueOf(&iface).Elem().Elem().Kind().String()) // Output: invalid // 标记3
var t time.Time
println(reflect.ValueOf(t).Kind().String()) // Output: struct // 标记4
}
标记1、2、3处怎么理解?
- 再次强调
reflect.ValueOf(x)
是函数调用; - 注意函数签名:
参数是func reflect.ValueOf(interface{}) reflect.Value
interface{}
,指所有调用该函数的地方,都会将参数类型转换成interface{}
。 interface{}
是typ, val
对,空的interface{}
是(nil, nil)
,类型和值都是空,所以Kind()
是invalid
;- 所有类型转换到
interface{}
的过程,都是(typ, val)
拷贝的过程; - 标记2处,
&iface
是一个interface
类型的指针; - 指针指向对象的类型是
interface
类型的变量; - 该interface类型的变量的
Kind()
是invalid
; - 所以标记3处是
invalid
。 - 标记4处,
time.Time
类型的底层是struct
,即:type Time struct { ... }
。
那如果说想获得类型名,而不是底层类型,该怎么处理呢?reflect.TypeOf(x)
:
func main() {
var i int
println(reflect.TypeOf(i).String()) // Output: int
var s string
println(reflect.TypeOf(s).String()) // Output: string
var iface interface{}
println(reflect.TypeOf(iface).String()) // panic
println(reflect.TypeOf(&iface).String()) // Output: *interface {}
println(reflect.TypeOf(&iface).Elem().String()) // Output: interface {}
var t time.Time
println(reflect.TypeOf(t).String()) // Output: time.Time
}
reflect.TypeOf(x).String()
一般用来处理time.Time
这种想要特殊处理的结构。
构造常用类型:
func main() {
intTyp := reflect.TypeOf(123)
strTyp := reflect.TypeOf("")
println(reflect.ArrayOf(10, intTyp).String()) // Output: [10]int
println(reflect.ChanOf(reflect.BothDir, intTyp).String()) // Output: chan int
println(reflect.MapOf(strTyp, intTyp).String()) // Output: map[string]int
println(reflect.PtrTo(intTyp).String()) // Output: *int
println(reflect.SliceOf(intTyp).String()) // Output: []int
println(reflect.StructOf([]reflect.StructField{
{Name: "IntA", Type: intTyp},
{Name: "StrB", Type: strTyp},
}).String()) // Output: struct { IntA int; StrB string }
var err error
errTyp := reflect.TypeOf(&err).Elem()
println(reflect.FuncOf([]reflect.Type{intTyp}, nil, false).String()) // Output: func(int)
println(reflect.FuncOf(nil, []reflect.Type{intTyp}, false).String()) // Output: func() int
println(reflect.FuncOf([]reflect.Type{intTyp}, []reflect.Type{strTyp, errTyp}, false).String()) // Output: func() (string, error)
println(reflect.FuncOf([]reflect.Type{reflect.SliceOf(strTyp)}, nil, true).String()) // Output: func(...string)
}
变量的内在机制
Go语言中的变量是分为两部分的:
- 类型信息:预先定义好的元信息。
- 值信息:程序运行过程中可动态变化的。
反射主要使用reflect包
在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的(我们在上一篇接口的博客中有介绍相关概念)。 在Go语言中反射的相关功能由内置的reflect
包提供,任意接口值在反射中都可以理解为由reflect.Type
和reflect.Value
两部分组成,并且reflect
包提供了reflect.TypeOf
和reflect.ValueOf
两个函数来获取任意对象的Value
和Type
。
TypeOf
在Go语言中,使用reflect.TypeOf()
函数可以获得任意值的类型对象reflect.Type
,程序通过类型对象可以访问任意值的类型信息。
func retType(i interface{}) {
//v数据类型*reflect.rtype
v := reflect.TypeOf(i)
fmt.Printf("Type:%T \nValue:%v \n", v, v)
}
func main() {
var a [2]string
retType(a)
}
另外,类型别名的话,只能拿到原类型。结构体等用type
定义的类型拿到的是type
后面的名字。具体看下例:
func retType(i interface{}) {
//v数据类型*reflect.rtype
v := reflect.TypeOf(i)
fmt.Printf("Type:%T \nValue:%v \n", v, v)
}
func main() {
var b [2]string
retType(b)
fmt.Println("--------------------")
//map类型
var c map[string]int16
retType(c)
fmt.Println("--------------------")
//也可以识别自定义数据类型
type t int64
var a t
retType(a)
fmt.Println("--------------------")
//结构体,拿到的是main.u
type u struct{}
d := u{}
retType(d)
fmt.Println("--------------------")
//类型别名拿到的是int8
type v = int8
var e v
retType(e)
fmt.Println("--------------------")
//传入自定义类型t会报错,编译不通过
//main.go|49 col 9 error| type t is not an expression
// retType(t)
fmt.Println("--------------------")
}
map
类型的b
,单纯的TypeOf(b)
显示map[string]int
,如果加上Key
的话,显示int
。结构体的话会带上包名,比如main.dog
。
type name和type kind
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type
关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。
func main() {
type dog struct {
eyes string
}
b := dog{eyes: "眼睛"}
// type a int64
// var b a
// b = 87
// a := "aaa"
// b := &a
ref := reflect.TypeOf(b)
fmt.Printf("ref \nType:%T Value:%v \n", ref, ref)
fmt.Printf("ref.Name \nType:%T Value:%v \n", ref.Name(), ref.Name())
fmt.Printf("ref.Kind \nType:%T Value:%v \n", ref.Kind(), ref.Kind())
}
如上例,TypeOf
返回的是main.dog
,Name
返回的是不带包名的dog
,Kind
返回的是种类struct
。Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()
都是返回空。
在reflect包中定义的Kind
类型如下:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
具体看如下代码,仅演示了结构体类型:
func main() {
type dog struct {
eyes string
fool int
}
b := dog{eyes: "眼睛", fool: 4}
ref := reflect.TypeOf(b)
// ref := reflect.TypeOf(&b)
//ref保存的是带有包名的b的数据类型main.dog,自身数据类型是reflect.Type
fmt.Printf("ref \nType:%T Value:%v \n", ref, ref)
//Name()保存的是不带包名的b的数据类型dog,自身数据类型是string
fmt.Printf("ref.Name \nType:%T Value:%v \n", ref.Name(), ref.Name())
//Kind保存的是b的种类struct,自身数据类型是reflect.Kind
fmt.Printf("ref.Kind \nType:%T Value:%v \n", ref.Kind(), ref.Kind())
//注意如果TypeOf(b)中的b是指针,需要使用Elem()方法找到指针指向的真实的结构体实体,才能进行下面两个操作
//NumField保存的是b中字段数量2,自身数据类型是int
fmt.Printf("ref.NumField \nType:%T Value:%v \n", ref.NumField(), ref.NumField())
//如果b是指针,效果同上
// fmt.Printf("ref.NumField \nType:%T Value:%v \n", ref.Elem().NumField(), ref.Elem().NumField())
//Field(0)保存的是第0个字段的各种信息,包括字段名,字段类型,字段tag等各种信息,自身数据类型是reflect.StructField
fmt.Printf("ref.Field(0) \nType:%T Value:%v \n", ref.Field(0), ref.Field(0))
fmt.Printf("ref.Field(1) \nType:%T Value:%v \n", ref.Field(1), ref.Field(1))
//如果b是指针,效果同上
// fmt.Printf("ref.Field(1) \nType:%T Value:%v \n", ref.Elem().Field(1), ref.Elem().Field(1))
}
ValueOf
reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。
方法 | 说明 |
---|---|
Interface() interface {} |
将值以interface{} 类型返回,可以通过类型断言转换为指定类型 |
Int() int64 |
将值以int 类型返回,所有有符号整型均可以此方式返回 |
Uint() uint64 |
将值以uint 类型返回,所有无符号整型均可以此方式返回 |
Float() float64 |
将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
Bool() bool |
将值以bool 类型返回 |
Bytes() []bytes |
将值以字节数组[]bytes 类型返回 |
String() string |
将值以字符串类型string 返回 |
类型、值操作
所有的类型都可以用Set方法修改值。
类型 | 方法 |
---|---|
实现接口 | Type.Implements(Type) bool |
assignable | Type.AssignableTo(Type) bool |
可转换类型 | Type.ConvertibleTo(Type) bool |
可比较 | Type.Comparable() bool |
basic types
类型 | 获取值 | 修改值 |
---|---|---|
int, int8, int16, int32, int64 |
Int() int64 |
SetInt(int64) |
uint, uint8, uint16, uint32, uint64 |
Uint() uint64 |
SeUint(uint64) |
string |
String() string |
SetString(string) |
bool |
Bool() bool |
SetBool(bool) |
float32, float64 |
Float() float64 |
SetFloat(float64) |
[]byte |
Bytes() []byte |
SetBytes([]byte) |
complex64, complex128 |
Complex() complex128 |
SetComplex(complex128) |
slice
操作 | 方法 |
---|---|
创建 |
gMakeSlice(Type, int, int) Value |
添加 |
gAppend(Value, ...Value) Value、AppendSlice(Value, Value) Value |
获取长度 |
gValue.Len() int |
设置长度 |
gValue.SetLen(int) |
获取容量 |
gValue.Cap() int |
设置容量 |
gValue.SetCap(int) |
切片 |
gValue.Slice(int, int) Value(也可用于string、array)、Slice3(也可用于array) |
下标获取 |
gValue.Index(int) Value |
拷贝 |
gCopy(Value, Value) |
生成交换函数(给sort包用) |
gSwapper(Value) func(int, int) |
是否为空 |
gValue.IsNil() bool |
类型 | 方法 |
---|---|
元素类型 |
Type.Elem() Type |
array
操作 | 方法 |
---|---|
创建 | New(Type) Value |
获取长度 | Value.Len() int |
类型 | 方法 |
---|---|
长度 | Type.Len() int |
元素类型 | Type.Elem() Type |
map
操作 | 方法 |
---|---|
创建 |
MakeMap(Type) Value、MakeMapWithSize(Type) Value |
添加/删除 |
Value.SetMapIndex(key, val Value)(删除时val为zero Value,可以写为reflect.Value{}) |
所有的键 |
Value.MapKeys() []Value |
获取值 |
Value.MapIndex(Value) Value |
获取长度 |
Value.Len() int |
是否为空 |
Value.IsNil() bool |
类型 | 方法 |
---|---|
键类型 | Type.Key() Type |
元素类型 | Type.Elem() Type |
chan
操作 | 方法 |
---|---|
创建 |
MakeChan(Type, int) Value |
发送 |
Value.Send(Value)、Value.TrySend(Value) bool |
接收 |
Value.Recv() (Value, bool)、Value.TryRecv() (Value, bool) |
关闭 |
Value.Close() |
获取长度 |
Value.Len() int |
是否为空 |
Value.IsNil() bool |
类型 | 方法 |
---|---|
方向 | Type.ChanDir() ChanDir |
元素类型 | Type.Elem() Type |
func
操作 | 方法 |
---|---|
创建 | MakeFunc(Type, func([]Value) []Value) Value |
调用 | Value.Call([]Value)[]Value、Value.CallSlice([]Value) []Value |
是否为空 | Value.IsNil() bool |
类型 | 方法 |
---|---|
变长参数 | Type.IsVariadic() bool |
参数个数 | Type.NumIn() int |
参数类型 | Type.In(int) Type |
返回值个数 | Type.NumOut() int |
返回值类型 | Type.Out(int) Type |
struct
操作 | 方法 |
---|---|
创建 |
New(Type) Value(New也可以用于创建基础类型如int等) |
字段数量 |
Value.NumField() int |
获取字段 |
Value.Field(int) Value、Value.FieldByName(string) Value、Value.FieldByIndex([]int) Value、FieldByFunc(func(string) bool) Value |
方法数量 |
Value.NumMethod() int |
获取方法 |
Value.Method(int) Value、Value.MethodByName(string) Value |
类型 | 方法 |
---|---|
方法数量 | Type.NumMethod |
获取方法详情 | Type.Method(int) Method·Type.MethodByName(string) Method |
是否实现接口 | Type.Implements(Type) bool |
字段详情 | Type.Field(int) StructField、Type.FieldByIndex([]int) StructField、Type.FieldByName(string) (StructField, bool)、Type.FieldByNameFunc(func(string) bool) (StructField, bool) |
ptr (interface类似)
操作 | 方法 |
---|---|
创建 | New(Type) Value(NewAt不列,不推荐用unsafe包) |
解指针 | Value.Elem() Value |
是否为空 | Value.IsNil() bool |
实例
链表
func ceshiSteuct(na string, age int, a interface{}) {
v := reflect.ValueOf(a)
// t := reflect.TypeOf(a)
//确保参数a是一个指针
if v.Type().Kind() != reflect.Ptr {
fmt.Println("结构体类型传参是传的值,所以必须要传指针进来")
return
}
//确保参数a是一个struct类型
if v.Elem().Type().Kind() != reflect.Struct {
fmt.Println("请确保参数a是一个struct类型")
return
}
if v.Elem().Type().Name() != "boy" {
fmt.Println("请确保参数a是一个结构体boy类型")
}
// //获取指定字段名的值
// // fmt.Println(v.Elem().FieldByName("Name"))
// x, ok := v.Elem().Type().FieldByName("Name")
// if ok {
// fmt.Printf("已经找到参数a中字段名为'Name'的字段\n值为:[%v] \n", x)
// }
//返回参数a的子类boy,字符串类型
// fmt.Printf("Type:%T \n", v.Elem().Type().Name())
//把参数a中的字段取出来存储到切片vfield中,方便后续操作
var vfield []reflect.Value
//返回参数a的内部字段数量
// fmt.Println(v.Elem().Type().NumField())
for i := 0; i < v.Elem().Type().NumField(); i++ {
//获取参数a的字段名字,如Name,Age等
// fmt.Println(v.Elem().Type().Field(i).Name)
//获取参数a的字段数据类型
// fmt.Println(v.Elem().Type().Field(i).Type)
//获取参数a的字段的tag
// fmt.Println(v.Elem().Type().Field(i).Tag)
//获取参数a的字段的指定Tag,例如下面获取的是ini的Tag
// fmt.Println(v.Elem().Type().Field(i).Tag.Get("ini"))
//v.Elen().Field(i)取出参数a中的第i个字段,reflect.Value类型
vfield = append(vfield, v.Elem().Field(i))
}
for _, n := range vfield {
//取得参数a各字段的值
// fmt.Printf("vfield: \nType:%T Value:%v \n", v, v)
//取得各字段的数据类型
// fmt.Printf("vfield: \nType:%T Value:%v \n", n.Type().Kind(), n.Type().Kind())
//修改参数a的各字段值
switch n.Kind() {
case reflect.String:
n.SetString(na)
case reflect.Int:
n.SetInt(int64(age))
default:
fmt.Println("暂时不支持的数据类型")
return
}
}
}
func main() {
type boy struct {
Name string `ini:"name"`
Age int `ini:"age"`
}
var li = boy{Name: "李雷", Age: 22}
fmt.Println(li)
//结构体和slice,map,chan不一样,结构体默认传值,必须要传指针进去才能进行下一步修改操作
ceshiSteuct("韩梅梅", 20, &li)
fmt.Println(li)
}
实例
用反射法值的简单实例:
func ceshiInt(a interface{}) {
//v取得动态值信息
v := reflect.ValueOf(a)
//查看v的动态值的类型信息
if v.Elem().Type().Kind() == reflect.Int {
//修改v的动态值
v.Elem().SetInt(29)
return
}
fmt.Println("失败")
}
func main() {
var i int = 3
fmt.Println(i)
ceshiInt(&i)
fmt.Println(i)
}
实例
p是一个bool指针,把b的地址传进函数怎么用反射法修改*p的值
func ceshiPrt(a interface{}) {
v := reflect.ValueOf(a)
//直接传p就走这里
if v.Elem().Kind() == reflect.Bool {
v.Elem().SetBool(true)
return
}
//这里是&p
fmt.Printf("不带Elem是p的地址 \nType:%T Value:%v \n", v, v)
//这里是&i
fmt.Printf("1个Elem是p的值,也就是i的地址 \nType:%T Value:%v \n", v.Elem(), v.Elem())
//这里才是真正的i的值
fmt.Printf("2个Elem才是i的值 \nType:%T Value:%v \n", v.Elem().Elem(), v.Elem().Elem())
if v.Elem().Elem().Kind() == reflect.Bool {
fmt.Println("找到根儿了")
v.Elem().Elem().SetBool(true)
return
}
fmt.Println("失败")
}
func main() {
//i的值是bool类型的false
var i bool = false
//p的值是i的地址
p := &i
fmt.Printf("&i:[%v] \n", &i)
fmt.Printf("&p:[%v] \n", &p)
//未修改的*p
fmt.Println(*p)
//传p的地址进去
ceshiPrt(&p)
//修改后的*p
fmt.Println(*p)
}
实例
数组类型用反射法修改数组元素的值:
func ceshiArray(a interface{}) {
//v取得动态值信息
v := reflect.ValueOf(a)
//也能拿到a的类型array
// fmt.Println(v.Elem().Kind())
//也能拿到a的元素长度3
// fmt.Println(v.Elem().Len())
//Index取得数组i的第0个元素
// fmt.Printf("Index \nType:%T Value:%v \n", v.Elem().Index(0), v.Elem().Index(0))
//获取数组i第0个元素的数据类型reflect.String
// fmt.Printf("Index.Kind \nType:%T Value:%v \n", v.Elem().Index(0).Kind(), v.Elem().Index(0).Kind())
//确保参数a是一个数组Array
if v.Elem().Kind() != reflect.Array {
fmt.Println("请确保参数a是一个数组")
return
}
//循环修改参数a的各元素的值
for i := 0; i < v.Elem().Len(); i++ {
if v.Elem().Index(i).Kind() != reflect.String {
fmt.Printf("请确保参数a的第%d元素是'string'类型。", i)
return
}
//修改数组元素的值
v.Elem().Index(i).SetString("mm")
}
}
func main() {
var i = [3]string{"aa", "bb", "cc"}
fmt.Println(i)
ceshiArray(&i)
fmt.Println(i)
}
实例
用反射法修改切片各元素的值
func ceshiSlice(a interface{}) {
v := reflect.ValueOf(a)
//确保参数a是一个指针
if v.Kind() != reflect.Ptr {
fmt.Println("参数a必须是一个指针,否则不能修改数据~")
}
//确认参数是slice类型
if v.Elem().Kind() != reflect.Slice {
fmt.Println("参数a必须是一个slice类型")
}
// 返回切片的len()
// fmt.Println(v.Elem().Len())
// 返回切片的cap()
// fmt.Println(v.Elem().Cap())
// 设置len(),不能超出cap,否则报错
// v.Elem().SetLen(3)
// 设置cap,不能小于len也不能大于原来的cap。也就是说cap只能变小,不能变大
// v.Elem().SetCap(3)
for i := 0; i < v.Elem().Len(); i++ {
//确保参数a的各元素都是string
if v.Elem().Index(i).Kind() != reflect.String {
fmt.Println("请确保参数a的元素都是string类型")
return
}
//修改参数a的各元素数据
v.Elem().Index(i).SetString("!xyz!")
}
}
func main() {
var i = []string{"aa", "bb", "cc", "dd"}
fmt.Printf("Type:%T Len:%d Cap:%d \nValue:%v \n", i, len(i), cap(i), i)
ceshiSlice(&i)
fmt.Printf("Type:%T Len:%d Cap:%d \nValue:%v \n", i, len(i), cap(i), i)
}
实例
用反射法操作map。修改key的对应值,删除key,清空所有key,value,添加key,value等操作
func ceshiMap(sli []string, a interface{}) {
//确保t的一致性,参数a可以是指针也可以不是指针
t := reflect.TypeOf(a)
v := reflect.ValueOf(a)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
//确保参数a的类型为map,key的类型为string
if t.Kind() != reflect.Map || t.Key().Kind() != reflect.String {
fmt.Println("请确保参数a是map类型,且key是string类型")
return
}
//确保map类型键值对儿的值为bool类型
if t.Elem().Kind() != reflect.Bool {
fmt.Println("请确保键值对儿的值为bool类型")
return
}
//获取v中所有的key以切片形式保存在keySlice中,keySlice中元素的数据类型是reflect.Value类型
keySlice := v.MapKeys()
//修改原有键值对儿的值
if len(keySlice) != 0 {
// //创建一个reflect.Bool类型的val,他的值为0。
val := reflect.New(reflect.TypeOf(true))
// //设置val的值为true
val.Elem().SetBool(false)
//修改一个原有key的值。
v.SetMapIndex(keySlice[0], val.Elem())
fmt.Printf("已将原有key为[%v]的值修改为[%v] \nValue:%v \n", keySlice[0], val.Elem(), v)
}
//删除一个原有键值对
if len(keySlice) != 0 {
//删除一个键值对儿,
v.SetMapIndex(keySlice[0], reflect.ValueOf(nil))
fmt.Printf("已删除了一个原有key为[%v]的键值对儿 \nValue:%v \n", keySlice[0], v)
}
//删除所有键值对儿
for _, s := range keySlice {
v.SetMapIndex(s, reflect.ValueOf(nil))
}
fmt.Printf("已清空了原有键值对儿 \nValue:%v \n", v)
// //创建一个reflect.String类型的key,它的值为0。注意,这里TypeOf("aa")中的aa可以是任意string类型字符串,不管是啥字符串,key的值都是0
// //key的值为空字符串,需要在遍历切片sli中对其赋值
key := reflect.New(reflect.TypeOf("aa"))
// //创建一个reflect.Bool类型的val,他的值为0。
val := reflect.New(reflect.TypeOf(true))
// //设置val的值为true
val.Elem().SetBool(true)
// 遍历切片sli
for _, s := range sli {
//设置key的值为"aa"
key.Elem().SetString(s)
//给v添加一个键值对,key是键,val是值
v.SetMapIndex(key.Elem(), val.Elem())
fmt.Println("已更新了所有原键值对儿")
}
// fmt.Println(t.Elem() == "book")
// fmt.Printf("keySlice \nType:%T Len:%d Cap:%d \n", keySlice, len(keySlice), cap(keySlice))
//创建一个reflect.String类型的key,它的值为0
// key := reflect.New(reflect.TypeOf("aa"))
//设置key的值为"aa"
// key.Elem().SetString("aa")
//创建一个reflect.Bool类型的val,他的值为0
// val := reflect.New(reflect.TypeOf(true))
//设置val的值为true
// val.Elem().SetBool(true)
//给v添加一个键值对,key是键,val是值
// v.SetMapIndex(key.Elem(), val.Elem())
//清空所有键值对
// v.SetMapIndex(keySlice[0], reflect.ValueOf(nil))
}
func main() {
i := make(map[string]bool, 5)
i = map[string]bool{"name": true, "eyes": true}
slice := []string{"aa", "bb", "cc"}
fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
//这里注意,slice,map,chan默认就是传地址
ceshiMap(slice, i)
fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
fmt.Println("--------------------")
fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
ceshiMap(slice, &i)
fmt.Printf("Type:%T len:%d \nValue:%v \n", i, len(i), i)
}
实例
反射法操作结构体
func ceshiSteuct(na string, age int, a interface{}) {
v := reflect.ValueOf(a)
// t := reflect.TypeOf(a)
//确保参数a是一个指针
if v.Type().Kind() != reflect.Ptr {
fmt.Println("结构体类型传参是传的值,所以必须要传指针进来")
return
}
//确保参数a是一个struct类型
if v.Elem().Type().Kind() != reflect.Struct {
fmt.Println("请确保参数a是一个struct类型")
return
}
if v.Elem().Type().Name() != "boy" {
fmt.Println("请确保参数a是一个结构体boy类型")
}
// //获取指定字段名的值
// // fmt.Println(v.Elem().FieldByName("Name"))
// x, ok := v.Elem().Type().FieldByName("Name")
// if ok {
// fmt.Printf("已经找到参数a中字段名为'Name'的字段\n值为:[%v] \n", x)
// }
//返回参数a的子类boy,字符串类型
// fmt.Printf("Type:%T \n", v.Elem().Type().Name())
//把参数a中的字段取出来存储到切片vfield中,方便后续操作
var vfield []reflect.Value
//返回参数a的内部字段数量
// fmt.Println(v.Elem().Type().NumField())
for i := 0; i < v.Elem().Type().NumField(); i++ {
//获取参数a的字段名字,如Name,Age等
// fmt.Println(v.Elem().Type().Field(i).Name)
//获取参数a的字段数据类型
// fmt.Println(v.Elem().Type().Field(i).Type)
//获取参数a的字段的tag
// fmt.Println(v.Elem().Type().Field(i).Tag)
//获取参数a的字段的指定Tag,例如下面获取的是ini的Tag
// fmt.Println(v.Elem().Type().Field(i).Tag.Get("ini"))
//v.Elen().Field(i)取出参数a中的第i个字段,reflect.Value类型
vfield = append(vfield, v.Elem().Field(i))
}
for _, n := range vfield {
//取得参数a各字段的值
// fmt.Printf("vfield: \nType:%T Value:%v \n", v, v)
//取得各字段的数据类型
// fmt.Printf("vfield: \nType:%T Value:%v \n", n.Type().Kind(), n.Type().Kind())
//修改参数a的各字段值
switch n.Kind() {
case reflect.String:
n.SetString(na)
case reflect.Int:
n.SetInt(int64(age))
default:
fmt.Println("暂时不支持的数据类型")
return
}
}
}
func main() {
type boy struct {
Name string `ini:"name"`
Age int `ini:"age"`
}
var li = boy{Name: "李雷", Age: 22}
fmt.Println(li)
//结构体和slice,map,chan不一样,结构体默认传值,必须要传指针进去才能进行下一步修改操作
ceshiSteuct("韩梅梅", 20, &li)
fmt.Println(li)
}
ini配置文件解析程序实例
ini配置文件
# 这里也是注释
[mysql]
address=192.168.1.12
port=1990
username=root
password=abcdefg
;[
#[]
;[ ]
# 这里是注释
[redis]
host=125.34.89.46
port=1990
username =root
password= abcdefg
moren=true
ini解析程序
package main
import (
"errors"
"fmt"
"io/ioutil"
"reflect"
"strconv"
"strings"
)
//反射解析ini配置文件联系
//mysql配置
type MysqlConfig struct {
Address string `ini:"address"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
}
//redis配置
type RedisConfig struct {
Host string `ini:"host"`
Port int `ini:"port"`
Username string `ini:"username"`
Password string `ini:"password"`
Moren bool `ini:"moren"`
}
//把mysqlConfig和redisConfig嵌套进一个大结构体Config内
type Config struct {
MysqlConfig `ini:"mysql" json:"MySql"`
RedisConfig `ini:"redis" json:"Redis"`
}
//打开文件读取文件内容
func ParsIniFile(fileName string, date interface{}) error {
//校验a的有效性
v := reflect.ValueOf(date)
//参数date必须是一个指针,因为在函数内要对date进行赋值
if v.Kind() != reflect.Ptr {
return errors.New("参数date必须是一个结构体指针")
}
//date必须是一个结构体
if v.Elem().Kind() != reflect.Struct {
return errors.New("参数date必须是一个结构体指针")
}
//打开文件读取文件,把文件全部读取到lineBytr切片中
fileByte, err := ioutil.ReadFile(fileName)
if err != nil {
return fmt.Errorf("读取%s文件失败。Err:%s \n", fileName, err)
}
// 把lineByte转换为字符串,并且以回车符"\n"分割为字符串切片
fileSlice := strings.Split(string(fileByte), "\n")
//保存参数data中的字段name,string类型
var fieldName string
//遍历读取到的文件内容
for index, filestr := range fileSlice {
//去除开头结尾的空白符
filestr = strings.TrimSpace(filestr)
//跳过空白行
if len(filestr) == 0 {
continue
}
//跳过";"和"#"开头的注释行
if strings.HasPrefix(filestr, ";") || strings.HasPrefix(filestr, "#") {
continue
}
//找到以"["开头的行当做"[mysql]"节处理
if strings.HasPrefix(filestr, "[") {
if !strings.HasSuffix(filestr, "]") {
return fmt.Errorf("文件[%s]第[%d]语法错误,缺少']'", fileName, index+1)
}
//拿到"[]"的内容,以去除首尾空格。
// 保存了从ini配置文件读取到的MysqlConfig结构体的Tag,数据类型是string
structNameTag := strings.TrimSpace(filestr[1 : len(filestr)-1])
//去掉首尾"["和"]"后,去掉首尾空格后计算长度,空字符串提示语法错误
if len(structNameTag) == 0 {
return fmt.Errorf("文件[%s]第[%d]语法错误,'[]'内长度为0", fileName, index+1)
}
//从主结构体参数data中取出各字段的Tag,和ini配置文件中取得的Tag做对比
for i := 0; i < v.Elem().NumField(); i++ {
//field依次获取到Config结构体内字段MysqlConfig和RedisConfig
if structNameTag == v.Elem().Type().Field(i).Tag.Get("ini") {
//取得参数data字段的name,string类型
fieldName = v.Elem().Type().Field(i).Name
//这里如果注释掉break,ini配置文件如有相同内容,下方内容优先。
break
}
}
} else {
//存储MysqlConfig结构体,数据类型reflect.Value
structField := v.Elem().FieldByName(fieldName)
//date必须是一个结构体
if structField.Type().Kind() != reflect.Struct {
return fmt.Errorf("参数date的字段%s必须是一个结构体", fieldName)
}
//处理=号左右的内容
//如果以"="开头提示语法错误
if strings.HasPrefix(filestr, "=") {
return fmt.Errorf("文件%s第%d行语法错误", fileName, index+1)
}
// 取出ini文件=号左右的内容,以=号分隔为切片
fieldTag := strings.Split(filestr, "=")
//按照从ini文件中读取到的fieldTag找到对应的结构体字段
for i := 0; i < structField.NumField(); i++ {
//判断从ini配置文件获取的MysqlConfig字段是否有效
if structField.Type().Field(i).Name != strings.TrimSpace(fieldTag[0]) && structField.Type().Field(i).Tag.Get("ini") != strings.TrimSpace(fieldTag[0]) {
continue
}
// //取得最终的字段例如Host,Uesename等信息,reflect.Value类型
field := structField.Field(i)
// fmt.Println(structField.Type().Field(i).Type)
// fmt.Println(field.Type().Kind())
//给结构体赋值
switch field.Type().Kind() {
//string类型直接赋值操作
case reflect.String:
field.SetString(strings.TrimSpace(fieldTag[1]))
//布尔类型赋值操作
case reflect.Bool:
var v bool
//用strconc包把字符串转换为系统支持的布尔类型
v, err = strconv.ParseBool(strings.TrimSpace(fieldTag[1]))
if err != nil {
return fmt.Errorf("ini配置文件%s第%d行语法错误,Bool类型仅支持如下语法:\n1,0,t,f,T,F,true,false,True,False,TRUE,FALSE\n。", fieldName, index+1)
}
//结构体字段赋值操作
field.SetBool(v)
//int类型用strconv包转换string为int类型后赋值操作
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var v int64
v, err = strconv.ParseInt(strings.TrimSpace(fieldTag[1]), 10, 64)
if err != nil {
return fmt.Errorf("ini配置文件%s第%d行语法错误,这里应该是一串数字\n。", fieldName, index+1)
}
field.SetInt(v)
//uint类型用strconv包转换string为int类型后赋值操作
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var v uint64
v, err = strconv.ParseUint(strings.TrimSpace(fieldTag[1]), 10, 64)
if err != nil {
return fmt.Errorf("ini配置文件%s第%d行语法错误,这里应该是一串数字\n。", fieldName, index+1)
}
field.SetUint(v)
}
}
}
}
return nil
}
func main() {
var iniConfig Config
// var cfg string
err := ParsIniFile("./a.ini", &iniConfig)
if err != nil {
fmt.Printf("Err:%v \n", err)
}
fmt.Println(iniConfig)
}
实例
func aaa(a interface{}) {
// ref := reflect.ValueOf(a)
// ref0 := ref.Elem().Field(0)
// ref.Type()
// fmt.Printf("ref: \nType:%T Value:%v \n", ref0, ref0.Type())
}
func main() {
// type dog struct {
// eyes string
// sex bool
// fool int
// }
// b := dog{eyes: "眼睛", fool: 4, sex: true}
// ret := reflect.ValueOf(b)
//ValueOf保存的是b中各字段的值:{眼睛 true 4}
// fmt.Printf("ret: \nType:%T Value:%v \n", ret, ret)
//Kind保存的是结构体b的种类struct,数据类型是reflect.Kind
// fmt.Printf("Kind: \nType:%T Value:%v \n", ret.Kind(), ret.Kind())
//NumField中结构体b中的字段数量3,数据类型是int
// fmt.Printf("NumField: \nType:%T Value:%v \n", ret.NumField(), ret.NumField())
//field中保存的是结构体b中第0个字段的值"眼睛",数据类型是reflect.Value
// fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0))
//Field.Type保存的是结构体b中第0个字段的类型信息string
// fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0).Type())
// fmt.Printf("Field: \nType:%T Value:%v \n", ret.Field(0), ret.Field(0).Elem())
// fmt.Printf("Elem \nType:%T Value:%v \n", ret.Elem(), ret.Elem())
// fmt.Printf("%v \n", ret.Field(0).CanSet())
//
// fmt.Println(b)
// aaa(&b)
// fmt.Println(b)
}