for i := 0; i < st.NumField(); i++ {
f := st.Field(i)
tag := f.Tag.Get("qson")
if (tag == "Name" || f.Name == "Name") && f.Type.Kind() == reflect.String {
foundName = true
break
}
}
if !foundName {
return
}
if niceField, foundHandsome := st.FieldByName("Handsome"); foundHandsome == false || niceField.Type.Kind() != reflect.Bool {
return
}
// 设置名字为 "qcrao" 的对象的 "Handsome" 字段为 true
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
handsome := e.FieldByName("Handsome")
// 寻找字段名为 Name 或者 tag 的值为 Name 的字段
var name reflect.Value
for j := 0; j < st.NumField(); j++ {
f := st.Field(j)
tag := f.Tag.Get("qson")
if tag == "Name" || f.Name == "Name" {
name = v.Index(i).Field(j)
}
}
if name.String() == "qcrao" {
handsome.SetBool(true)
}
}
}
func main() {
children := []Child{
{Name: "Ava", Grade: 3, Handsome: true},
{Name: "qcrao", Grade: 6, Handsome: false},
}
adults := []Adult{
{ID: "Steve", Occupation: "Clerk", Handsome: true},
{ID: "qcrao", Occupation: "Go Programmer", Handsome: false},
}
fmt.Printf("adults before handsome: %v\n", adults)
handsome(adults)
fmt.Printf("adults after handsome: %v\n", adults)
fmt.Println("-------------")
fmt.Printf("children before handsome: %v\n", children)
handsome(children)
fmt.Printf("children after handsome: %v\n", children)
}
代码运行结果:
adults before handsome: [{Steve Clerk true} {qcrao Go Programmer false}]
adults after handsome: [{Steve Clerk true} {qcrao Go Programmer true}]
-------------
children before handsome: [{Ava 3 true} {qcrao 6 false}]
children after handsome: [{Ava 3 true} {qcrao 6 true}]
代码主要做的事情是:找出传入的参数为 Slice,并且 Slice 的元素为结构体,如果其中有一个字段名是 Name
或者是 标签名称为 Name
,并且还有一个字段名是 Handsome
的情形。如果找到,并且字段名称为 Name
的实际值是 qcrao
的话,就把另一个字段 Handsome
的值置为 true。
程序并不关心传入的结构体到底是什么,只要它的字段名包含 Name
和 Handsome
,都是 handsome 函数要工作的对象。
注意一点,Adult
结构体的标签 qson:"Name"
,中间是没有空格的,否则 Tag.Get("qson")
识别不出来。
未导出成员
利用反射机制,对于结构体中未导出成员,可以读取,但不能修改其值。
注意,正常情况下,代码是不能读取结构体未导出成员的,但通过反射可以越过这层限制。另外,通过反射,结构体中可以被修改的成员只有是导出成员,也就是字段名的首字母是大写的。
一个可取地址的 reflect.Value 变量会记录一个结构体成员是否是未导出成员,如果是的话则拒绝修改操作。
CanAddr 不能说明一个变量是否可以被修改。
CanSet 则可以检查对应的 reflect.Value 是否可取地址并可被修改。
package main
import (
"reflect"
"fmt"
)
type Child struct {
Name string
handsome bool
}
func main() {
qcrao := Child{Name: "qcrao", handsome: true}
v := reflect.ValueOf(&qcrao)
f := v.Elem().FieldByName("Name")
fmt.Println(f.String())
f.SetString("stefno")
fmt.Println(f.String())
f = v.Elem().FieldByName("handsome")
// 这一句会导致 panic,因为 handsome 字段未导出
//f.SetBool(true)
fmt.Println(f.Bool())
}
执行结果:
qcrao
stefno
true
上面的例子中,handsome 字段未导出,可以读取,但不能调用相关 set 方法,否则会 panic。反射用起来一定要小心,调用类型不匹配的方法,会导致各种 panic。
反射的实际应用
反射的实际应用非常广:IDE 中的代码自动补全功能、对象序列化(json 函数库)、fmt 相关函数的实现、ORM(全称是:Object Relational Mapping,对象关系映射)……
这里举 2 个例子:json 序列化和 DeepEqual 函数。
json 序列化
开发过 web 服务的同学,一定用过 json
数据格式。json
是一种独立于语言的数据格式。最早用于浏览器和服务器之间的实时无状态的数据交换,并由此发展起来。
Go 语言中,主要提供 2 个函数用于序列化和反序列化:
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data [