前言 反射(reflection)是在 Java 出现后迅速流行起来的一种概念,大多数现代的高级语言都以各种形式支持反射功能,但反射是把双刃剑,功能强大代码却不清晰。
各语言对反射的支持:
C/C++ 语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息;
Java、C# 等语言都支持完整的反射功能;
Lua、JavaScript 等动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统;
Go 语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。 Go语言中的反射是由reflect
包提供支持的,反射最重要的两个概念:Type
和Value
。
reflect.Type
可以获取类型信息,通过TypeOf()
函数得到;
reflect.Value
可以获取和修改值,通过valueOf()
函数得到;
在反射中往往会判断类型(Type)与种类(Kind):
当需要区分一个大的种类时,就会用到种类 Kind;
当需要根据类型创建一个变量时,就需要用到类型 Type;
type 包括系统原生数据类型,如 int、string、bool、float64、map、struct 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型;
所有的种类(Kind)在reflect/type.go
(line:241) 中有定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 type Kind uint 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 )
基本数据类型与反射类型之间的转换如下:
1. reflect.Type 公共结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type People interface { GetName() string GetAge() int } type User struct { Name string `json:"name"` age int `json:"age"` ContactInfo ContactInfo `json:"contact_info"` } func (u User) GetName() string { return u.Name } func (u *User) GetAge() int { return u.age } type ContactInfo struct { Address string Phone string } func Add (a, b int ) int { return a + b }
1.1 获取 Type 1 2 3 4 5 6 7 fmt.Println(reflect.TypeOf(1 )) fmt.Println(reflect.TypeOf("hello" )) utype := reflect.TypeOf(User{}) uptrType := reflect.TypeOf(&User{}) fmt.Printf("User Type: %s, Kind: %s\n" , utype, utype.Kind()) fmt.Printf("*User Type: %s, Kind: %s, Elem Kind: %s\n" , uptrType, uptrType.Kind(), uptrType.Elem().Kind()) fmt.Println(uptrType.Elem().Kind() == utype.Kind())
输出:
1 2 3 4 int string User Type: main.User, Kind: struct *User Type: *main.User, Kind: ptr, Elem Kind: struct
解析:
1.2 获取 struct 成员信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 utype := reflect.TypeOf(User{}) uptrType := reflect.TypeOf(&User{}) fieldNum := utype.NumField() for i := 0 ; i < fieldNum; i++ { field := utype.Field(i) fmt.Printf("index: %d, name: %s, offset: %d, anonymous: %t, type: %s, exported: %t, json tag: %s\n" , i, field.Name, field.Offset, field.Anonymous, field.Type, field.IsExported(), field.Tag.Get("json" )) } if name, ok := utype.FieldByName("Name" ); ok { fmt.Printf("Name is exported: %t\n" , name.IsExported()) } field := utype.FieldByIndex([]int {2 , 1 }) fmt.Printf("User.ContactInfo second field name: %s\n" , field.Name) tpuMethodNum := utype.NumMethod() for i := 0 ; i < tpuMethodNum; i++ { method := utype.Method(i) fmt.Printf("[User method] name: %s,type: %s, exported: %t\n" , method.Name, method.Type, method.IsExported(), ) } tpupMethodNum := uptrType.NumMethod() for i := 0 ; i < tpupMethodNum; i++ { method := uptrType.Method(i) fmt.Printf("[*User method] name: %s, type: %s, exported: %t\n" , method.Name, method.Type, method.IsExported(), ) }
输出:
1 2 3 4 5 6 7 8 index: 0 , name: Name, offset: 0 , anonymous: false , type : string , exported: true , json tag: name index: 1 , name: age, offset: 16 , anonymous: false , type : int , exported: false , json tag: age index: 2 , name: ContactInfo, offset: 24 , anonymous: false , type : main.ContactInfo, exported: true , json tag: contact_info Name is exported: true User.ContactInfo second field name: Phone [User method] name: GetName,type : func (main.User) string , exported: true [*User method] name: GetAge, type : func (*main.User) int , exported: true [*User method] name: GetName, type : func (*main.User) string , exported: true
解析:
FieldByIndex()
方法参数是一个数组,是因为结构体会有嵌套,数组中元素值就是结构体成员下标,依次深入取值。如果在访问最后一个下标前,成员不是 struct,则会引发 panic;
这里 User 的 Type 只能获取到一个方法,而 *User 的 Type 可以获取到两个方法,原因是GetAge()
方法的接收器是指针类型(*User)。值类型(User)接收器可以被值类型(User)和指针类型(*User)调用,但指针类型(*User)接收器只能被指针类型(*User)调用。因此 User 无法调用GetAge()
方法,即 User 没有GetAge()
方法;
1.3 获取函数信息 1 2 3 4 5 6 7 8 9 10 11 12 fType := reflect.TypeOf(Add) fmt.Printf("Add() kind: %s, is function: %t\n" , fType.Kind(), fType.Kind() == reflect.Func) inNum := fType.NumIn() for i := 0 ; i < inNum; i++ { tp := fType.In(i) fmt.Printf("[args in] index: %d, type: %s\n" , i, tp) } outNum := fType.NumOut() for i := 0 ; i < outNum; i++ { tp := fType.Out(i) fmt.Printf("[args out] index: %d, type: %s\n" , i, tp) }
输出:
1 2 3 4 Add() kind: func , is function : true [args in] index: 0 , type : int [args in] index: 1 , type : int [args out] index: 0 , type : int
解析:
返回值是个数组,是因为 Go 的返回值可以有多个;
1.4 判断类型是否实现了某个接口 1 2 3 4 5 6 7 8 9 utype := reflect.TypeOf(User{}) uptrType := reflect.TypeOf(&User{}) ppType := reflect.TypeOf((*People)(nil )).Elem() fmt.Printf("[People] kind: %s, is interface: %t\n" , ppType.Kind(), ppType.Kind() == reflect.Interface) fmt.Printf("[User] implements People interface: %t\n" , utype.Implements(ppType)) fmt.Printf("[*User] implements People interface: %t\n" , uptrType.Implements(ppType))
输出:
1 2 3 [People] kind: interface , is interface : true [User] implements People interface : false [*User] implements People interface : true
解析:
上面提到User
没有GetAge()
方法,所以它没有实现People
接口,自然也不是People
类型;
2. reflect.Value 2.1 获取 Value 1 2 3 4 5 6 fmt.Println(reflect.ValueOf(1 )) fmt.Println(reflect.ValueOf("hello" )) uValue := reflect.ValueOf(User{}) fmt.Println(uValue) uptrValue := reflect.ValueOf(&User{}) fmt.Println(uptrValue)
输出:
1 2 3 4 1 hello { 0 { }} &{ 0 { }}
解析:
Value 中包含的是变量的值,而 Type 中包含的是变量的元信息;
2.2 Value 转为 Type 1 2 3 4 uValue := reflect.ValueOf(User{Name: "Bobby" , age: 22 }) fmt.Println(uValue.Kind(), uValue.Type().Kind(), uValue.Kind() == uValue.Type().Kind()) uptrValue := reflect.ValueOf(&User{Name: "Bobby" , age: 22 }) fmt.Println(uptrValue.Kind(), uptrValue.Type().Kind(), uptrValue.Kind() == uptrValue.Type().Kind())
输出:
1 2 struct struct true ptr ptr true
解析:
在 Type 和 Value 上调用 Kind 方法,结果是一样的;
2.3 指针 Value 转换值 Value 1 2 3 4 5 6 7 uptrValue := reflect.ValueOf(&User{}) elem := uptrValue.Elem() fmt.Println(uptrValue.Kind(), elem.Kind()) ptr := elem.Addr() fmt.Println(ptr.Kind(), elem.Kind()) fmt.Println(reflect.ValueOf(User{}).Addr().Kind())
输出:
1 2 3 ptr struct ptr struct panic : reflect.Value.Addr of unaddressable value
解析:
对一个指针 Value 可以使用Elem()
和Addr()
相互转换;
于一个值 Value 使用Addr()
会引发 panic;
2.4 得到 Value 原始的数据 1 2 3 4 5 6 7 8 fmt.Println(reflect.ValueOf(1 ).Interface().(int ), reflect.ValueOf(1 ).Int()) fmt.Println(reflect.ValueOf("hello" ).Interface().(string ), reflect.ValueOf("hello" ).String()) uValue := reflect.ValueOf(User{Name: "Bobby" , age: 22 }) uptrValue := reflect.ValueOf(&User{Name: "Regan" , age: 26 }) user := uValue.Interface().(User) fmt.Println(user.Name, user.age) userPtr := uptrValue.Interface().(*User) fmt.Println(userPtr.Name, userPtr.age)
输出:
1 2 3 4 1 1 hello hello Bobby 22 Regan 26
解析:
使用 Interface() 方法将 reflect.Value 变成 interface{};
可以使用类型强转,或对应基本类型的方法Int()
、String()
等,一步到位获取原始值;
2.5 空 Value 判断 1 2 3 4 5 6 7 8 9 10 11 12 13 var eface interface {}iValue := reflect.ValueOf(eface) fmt.Printf("[interface{}] IsValid: %t, vaifa kind is Invalid: %t\n" , iValue.IsValid(), iValue.Kind() == reflect.Invalid) var uptr *User = nil uptrValue := reflect.ValueOf(uptr) if uptrValue.IsValid() { fmt.Printf("[*User] IsNil: %t, IsZero: %t\n" , uptrValue.IsNil(), uptrValue.IsZero()) } var u UseruValue := reflect.ValueOf(u) if uValue.IsValid() { fmt.Printf("[User] IsZero: %t\n" , uValue.IsZero()) }
输出:
1 2 3 [interface {}] IsValid: false , vaifa kind is Invalid: true [*User] IsNil: true , IsZero: true [User] IsZero: true
解析:
在调用IsNil()
和IsZero()
之前,一定要确保uValue.IsValid()
为真,否则会引发 panic;
对于指针 Value 可以调用IsNil()
和IsZero()
,但对于值 Value 只能调用IsZero()
,若调用IsNil()
会引发 panic,因为值类型默认有零值,不能为 nil;
2.6 修改 Value 原始数值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var i int = 10 iValue := reflect.ValueOf(&i) iValue.Elem().SetInt(8 ) fmt.Println(i) var s string = "hello" sValue := reflect.ValueOf(&s) sValue.Elem().SetString("world" ) fmt.Println(s) user := User{Name: "Bobby" , age: 22 } uValue := reflect.ValueOf(&user) uValue.Elem().FieldByName("Name" ).SetString("Regan" ) fmt.Println(user.Name) fieldAge := uValue.Elem().FieldByName("age" ) if fieldAge.CanSet() { fieldAge.SetInt(26 ) } else { fmt.Println("User.age 未导出,不能被修改" ) }
输出:
1 2 3 4 8 world Regan User.age 未导出,不能被修改
解析:
Go 默认是值传递,所以修改值时必须传指针;
使用Elem()
方法获取指针指向的实际变量,才能修改变量值;
结构体中未导出的成员不能被修改;
2.7 修改 slice Value 1 2 3 4 5 6 7 8 9 10 11 12 13 14 users := make ([]*User, 1 , 5 ) users[0 ] = &User{Name: "Bobby" , age: 22 } usValue := reflect.ValueOf(&users) if usValue.Elem().Len() > 0 { usValue.Elem().Index(0 ).Elem().FieldByName("Name" ).SetString("Regan" ) fmt.Printf("first *User.Name change to: %s\n" , users[0 ].Name) } usValue.Elem().SetCap(3 ) usValue.Elem().SetLen(2 ) usValue.Elem().Index(1 ).Set(reflect.ValueOf(&User{Name: "Mayee" , age: 20 })) fmt.Printf("second *User.Name: %s\n" , users[1 ].Name)
输出:
1 2 first *User.Name change to: Regan second *User.Name: Mayee
解析:
可以修改切片的 cap,但新的 cap 只能改小,且值只能在原 len 和 cap 之间;
可以把 len 改大,但不能超过 cap;
2.8 修改 map Value 1 2 3 4 5 6 7 8 9 10 11 umap := make (map [int ]*User, 5 ) umap[1 ] = &User{Name: "Bobby" , age: 22 } mValue := reflect.ValueOf(&umap) mValue.Elem().SetMapIndex(reflect.ValueOf(2 ), reflect.ValueOf(&User{Name: "Regan" , age: 26 })) mValue.Elem().MapIndex(reflect.ValueOf(1 )).Elem().FieldByName("Name" ).SetString("Mayee" ) for k, u := range umap { fmt.Printf("key: %d, *User.Name: %s\n" , k, u.Name) }
输出:
1 2 key: 2 , *User.Name: Regan key: 1 , *User.Name: Mayee
解析:
map 中的 v 必须是指针才可以被直接修改,若是值类型这回引发panic: reflect: call of reflect.Value.Elem on struct Value
,因为值类型在 map 中是不可寻址的;
2.9 通过 Value 调用函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 fValue := reflect.ValueOf(Add) fType := reflect.TypeOf(Add) inNum := fType.NumIn() args := make ([]reflect.Value, inNum) for i := 0 ; i < inNum; i++ { if fType.In(i).Kind() == reflect.Int { args[i] = reflect.ValueOf(3 ) } } out := fValue.Call(args) if fType.Out(0 ).Kind() == reflect.Int { sum := out[0 ].Int() fmt.Printf("Add() return: %d\n" , sum) }
输出:
解析:
2.10 通过 Value 调用成员方法 1 2 3 4 5 6 7 8 9 10 11 uptrValue := reflect.ValueOf(&User{Name: "Bobby" , age: 22 }) methodAge := uptrValue.MethodByName("GetAge" ) ageOut := methodAge.Call([]reflect.Value{}) fmt.Printf("[*User] GetAge() return age: %d\n" , ageOut[0 ].Int()) methodName := uptrValue.MethodByName("GetName" ) nameOut := methodName.Call([]reflect.Value{}) fmt.Printf("[*User] GetName() return name: %s\n" , nameOut[0 ].String()) uValue := reflect.ValueOf(User{Name: "Regan" , age: 26 }) method := uValue.MethodByName("GetName" ) call := method.Call([]reflect.Value{}) fmt.Printf("[User] GetName() return name: %s\n" , call[0 ].String())
输出:
1 2 3 [*User] GetAge() return age: 22 [*User] GetName() return name: Bobby [User] GetName() return name: Regan
解析:
调用成员方法时,若调用了类型不存在的方法会引发 panic;
2.11 通过 Value 创建 struct 1 2 3 4 5 uType := reflect.TypeOf(User{}) uValue := reflect.New(uType) uValue.Elem().FieldByName("Name" ).SetString("Bobby" ) u := uValue.Interface().(*User) fmt.Printf("User.Name: %s\n" , u.Name)
输出:
解析:
New()
方法可以将 Type 转为 Value,得到的是指针类型的 Value;
2.12 通过 Value 创建 slice 1 2 3 4 5 usType := reflect.TypeOf([]User{}) usValue := reflect.MakeSlice(usType, 1 , 3 ) usValue.Index(0 ).Set(reflect.ValueOf(User{Name: "Bobby" , age: 22 })) us := usValue.Interface().([]User) fmt.Printf("first User.Name: %s\n" , us[0 ].Name)
输出:
解析:
使用Index()
方法指定下标向切片插入元素;
MakeSlice
必须指定 len 和 cap;
2.13 通过 Value 创建 map 1 2 3 4 5 6 mType := reflect.TypeOf(map [int ]*User{}) mValue := reflect.MakeMapWithSize(mType, 10 ) mValue.SetMapIndex(reflect.ValueOf(1 ), reflect.ValueOf(&User{Name: "Bobby" , age: 22 })) mValue.MapIndex(reflect.ValueOf(1 )).Elem().FieldByName("Name" ).SetString("Regan" ) umap := mValue.Interface().(map [int ]*User) fmt.Printf("User.Name: %s\n" , umap[1 ].Name)
输出:
解析:
创建 map 时也可以使用reflect.MakeMap()
方法不指定 len;
2.14 通过 Value 创建函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var fn func ([]int ) (int , error ) fnValue := reflect.ValueOf(&fn).Elem() makeFn := reflect.MakeFunc(fnValue.Type(), func (args []reflect.Value) (results []reflect.Value) { in := args[0 ] if in.Kind() != reflect.Slice { return []reflect.Value{reflect.ValueOf(0 ), reflect.ValueOf(errors.New("入参必传是切片" ))} } if in.Index(0 ).Kind() != reflect.Int { return []reflect.Value{reflect.ValueOf(0 ), reflect.ValueOf(errors.New("切片类型必须是 int" ))} } out := 0 for i := 0 ; i < in.Len(); i++ { el := in.Index(i).Interface().(int ) out += el } nilOut := reflect.Zero(reflect.TypeOf((*error )(nil )).Elem()) return []reflect.Value{reflect.ValueOf(out), nilOut} }) fnValue.Set(makeFn) if ret, err := fn([]int {1 , 2 , 3 }); err != nil { fmt.Println(err.Error()) } else { fmt.Println(ret) }
输出:
解析:
3. 防坑实践 3.1 nil ≠ nil 开发中有时候会出现返回值明明是 nil,但是判断非空时却是 false。下面看一个实际的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package mainimport "fmt" type BizError struct { Code int Msg string } func (e *BizError) Error() string { return fmt.Sprintf("code: %d, msg: %s" , e.Code, e.Msg) } type Biz struct {} func (b *Biz) First() error { fmt.Println("do First." ) return nil } func (b *Biz) Second() *BizError { fmt.Println("do Second." ) return nil } func (b *Biz) Finish() { fmt.Println("do Finish." ) } func main () { b := new (Biz) err := b.First() if err != nil { fmt.Println("do First error" ) return } if err = b.Second(); err != nil { fmt.Println("do Second error" ) return } b.Finish() }
代码解析:
我们自定义了一个错误BizError
表示业务异常,并实现了error
接口;
业务Biz
有 3 个方法First()
、Second()
和Finish()
,其中First()
会返回error
接口类型,Second()
会返回*BizError
类型,但BizError
类型实现了error
接口,所以它的值也是可以用error
接收;
First()
、Second()
方法中最后都是返回了 nil,理论上会走到Finish()
方法,但实际输出是:
1 2 3 do First. do Second. do Second error
what? Second()
明明返回的是 nil 啊,为什么在判断非空时却为真呢?难道这个 nil 有什么奇妙之处?
对于一个变量来说,它有两个要素:Type 和 Value。来看一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 var it interface {}fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) it = 1 fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) it = "hello" fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) var s *string it = s fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it)) ss := "hello" it = &ss fmt.Println(reflect.TypeOf(it), reflect.ValueOf(it))
输出:
1 2 3 4 5 <nil > <invalid reflect.Value> int 1 string hello*string <nil > *string 0xc00005c250
在给一个 interface 变量赋值前,Type 和 Value 都是 nil,但给它赋值后,不仅会改变它的 Value,还会改变它的 Type。 当把一个值为 nil 的字符串指针赋值给它后,虽然它的值是 Value 是 nil,但它的类型 Type 却变成了 *string。 此时如果拿它来跟 nil 比较,结果就会是不相等,因为只有当这个 interface 变量的类型和值都未被设置时,它才真正等于 nil。
再来看看之前的例子中,err 变量的 Type 和 Value 是如何变化的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { b := new (Biz) err := b.First() fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) if err != nil { fmt.Println("do First error" ) return } if err = b.Second(); err != nil { fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) fmt.Println("do Second error" ) return } b.Finish() }
输出:
1 2 3 4 5 do First. <nil > <invalid reflect.Value> do Second. *main.BizError <nil > do Second error
在一开始,我们给 err 初始化赋值时,First()
方法返回的是一个error
接口类型的 nil,此时查看其 Type 和 Value 时,都会是 nil。 而在调用Second()
方法时,会将一个*BizError
类型的 nil 值赋值给了它,此时它的 Type 便成了*BizError
,Value 是 nil。 所以在做 err ≠ nil 的比较时,err 的 Type 已经不是 nil,前面已经说过,只有当一个接口变量的 Type 和 Value 同时为 nil 时,这个变量才会被判定为 nil,所以该不等式会判定为 true。
要修复这个问题,其实最简单的方法便是在调用Second()
方法时给 err 进行重新声明:
1 2 3 4 5 if err := b.Second(); err != nil { fmt.Println(reflect.TypeOf(err), reflect.ValueOf(err)) fmt.Println("do Second error" ) return }
此时,err 其实成了一个新的结构体指针变量,而不再是一个interface
类型变量,类型为*BizError
,且值为 nil,所以做 err ≠ nil 的比较时结果就是将是 false。
3.2 map 元素不可寻址 在开发中,当我们想修改 map 中的元素时可以怎么做呢? 下面看个例子:
1 2 3 4 5 6 7 func main () { m := map [int ]string { 1 : "A" , 2 : "B" , } fmt.Printf("%p\n" ,&m[1 ]) }
此时程序编译是不通过的,会提示无法获取&m[1]
的地址。 下面再看一个例子:
1 2 3 4 5 6 7 8 func main () { m := map [int ]string { 1 : "A" , 2 : "B" , } m[1 ] = "C" fmt.Println(m) }
此时 map 中的元素被成功修改。那这就有个疑惑:既然不能寻址,为何又可以修改呢? 我们接着看第三个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { m := map [int ]Person{ 1 : Person{"Andy" , 10 }, 2 : Person{"Tiny" , 20 }, 3 : Person{"Jack" , 30 }, } m[1 ].name = "TOM" fmt.Println(m) } type Person struct { name string age int }
此时程序编译是不通过的,会提示无法分配给m[1].name
地址。为什么这里又不能修改元素值了? 因为这个时候的m[1]
是一个结构体Person{"Andy", 10}
,它是不可寻址的,你要对不可寻址的结构体进行.name
操作自然会报错。 它与第二个例子还是有区别的,在第二个例子中我们相当于是覆盖了m[1]
这个值,而在本例中修改的是m[1]
中的值,这个值无法被寻址,所以不能修改。
如果要对结构体修改值,可以通过新建变量,修改后再覆盖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { m := map [int ]Person{ 1 : Person{"Andy" , 10 }, 2 : Person{"Tiny" , 20 }, 3 : Person{"Jack" , 30 }, } p := m[1 ] p.name = "TOM" m[1 ] = p fmt.Println(m[1 ]) } type Person struct { name string age int }
或则可以通过指针 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { m := map [int ]*Person{ 1 : &Person{"Andy" , 10 }, 2 : &Person{"Tiny" , 20 }, 3 : &Person{"Jack" , 30 }, } m[1 ].name = "TOM" fmt.Println(m[1 ].name) } type Person struct { name string age int }
因为 map 中存的是Person
的指针,所以它是可以被寻址的。
Tip:本文完整示例代码已上传至 Gitee