Go 使用反射获取指向值的指针
我有一个函数,它遍历作为参数传递的接口的所有字段。为了实现这一点,我正在使用反射。问题是我不知道如何获取非指针字段的地址。以下是一个例子:Go 使用反射获取指向值的指针,go,reflection,Go,Reflection,我有一个函数,它遍历作为参数传递的接口的所有字段。为了实现这一点,我正在使用反射。问题是我不知道如何获取非指针字段的地址。以下是一个例子: type Z struct { Id int } type V struct { Id int F Z } type T struct { Id int F V } 上面的代码表示我的测试结构。下面是实际的函数,它遍历指定的结构并列出有关它的详细信息: func InspectStruct(o interface
type Z struct {
Id int
}
type V struct {
Id int
F Z
}
type T struct {
Id int
F V
}
上面的代码表示我的测试结构。下面是实际的函数,它遍历指定的结构并列出有关它的详细信息:
func InspectStruct(o interface{}) {
val := reflect.ValueOf(o)
if val.Kind() == reflect.Interface && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
val = elm
}
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
address := "not-addressable"
if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
valueField = elm
}
}
if valueField.Kind() == reflect.Ptr {
valueField = valueField.Elem()
}
if valueField.CanAddr() {
address = fmt.Sprint(valueField.Addr().Pointer())
}
fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
if valueField.Kind() == reflect.Struct {
InspectStruct(valueField.Interface())
}
}
}
最后是InspectStruct调用的输出:
Field Name: Id, Field Value: 1, Address: 408125440 , Field type: int , Field kind: int
Field Name: F, Field Value: {2 {3}}, Address: 408125444 , Field type: main.V , Field kind: struct
Field Name: Id, Field Value: 2, Address: not-addressable , Field type: int , Field kind: int
Field Name: F, Field Value: {3}, Address: not-addressable , Field type: main.Z , Field kind: struct
Field Name: Id, Field Value: 3, Address: not-addressable , Field type: int , Field kind: int
正如您所看到的,我正在使用递归,所以如果其中一个字段是结构类型的,那么我将为它调用InspectStruct。
我的问题是,尽管整个结构“t”层次结构的所有字段都已初始化,但我无法获取深度高于“t”的任何字段的地址。我非常感谢您的帮助。传递
reflect.Value
而不是interface{}
似乎可以解决问题,但是我不知道为什么valueField.interface()
不起作用
工作示例:
func InspectStructV(val reflect.Value){
如果val.Kind()==reflect.Interface&!val.IsNil(){
elm:=val.Elem()
如果elm.Kind()==reflect.Ptr&&!elm.IsNil()&&elm.Elem().Kind()==reflect.Ptr{
瓦尔=埃尔姆
}
}
如果val.Kind()==reflect.Ptr{
val=val.Elem()
}
对于i:=0;i
接口()不起作用的原因是它返回的接口包装器。为了让大家了解正在发生的事情,让我们看看我们正在做什么,而不是反思:
type MyStruct struct {
F Foo
}
type Foo struct {
i int
}
func ExtractField(ptr *MyStruct) interface{} {
return ptr.F
}
func main() {
ms := &MyStruct{Foo{5}}
f := ExtractField(ms).(Foo) // extract value
f.i = 19
fmt.Println(f, ms.F) // ???
fmt.Println(&f == &ms.F) // Not the same!
}
()
但是,考虑一下它返回的接口{}
。它是什么?ptr.F
的值,即它的副本。这就是value.Interface
所做的,它返回Interface{}
包装字段。不再有任何指针元数据,它与原始结构完全分离
正如您所注意到的,对于“顶层”的CanAddr,toreflect.ValueOf
将始终返回false
——因为该地址没有意义,因为它将为您提供值副本的地址,更改它实际上没有任何意义。(请记住,指针也是值——如果您想要像*Foo
这样的指针值字段的地址,您实际上是在寻找**Foo
)
因此,在上面的示例中,如果我们天真地传入reflect.ValueOf(ExtractField(ms))
,我们将得到ValueOf
f
,它不仅没有您想要的地址,而且根据reflect它甚至不可寻址,因为就reflect而言,永远不会给出有效的地址(它能提供给您的唯一地址是value
struct中内部值副本的地址)
那么为什么将值
传递到兔子洞中会起作用呢?好吧,唯一真正的说法是reflect.Value
在使用Elem
和字段
时维护必要的元数据,而接口{}
则不能。因此,reflect.Value
可能看起来像:
// Disclaimer: not the real structure of a reflect.Value
type Value struct {
fieldAddress uintptr
value Foo
}
它能给你的只有这个
// Again, an abstraction of the real interface wrapper
// just for illustration purposes
type interface{} struct {
value Foo
}
@OneofOne的答案很完美,但最好再加一个检查
if valueField.IsValid() {
fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
}
这是必要的,因为有时您可以从零值结构中请求接口。一旦发生这种情况,它就会惊慌失措。我今天遇到了一个反射兔子洞,通过研究此代码和LinearZoetrope的回答,学到了很多东西,谢谢。但我对您的问题得出了不同的结论,这可能导致了更直接的问题解决方案:
1) 最初调用函数时,您正在传递指向结构的指针,但是
2) 当通过调用“InspectStruct(valueField.Interface())”进行递归时,不是通过指针传递嵌入式结构,而是通过值传递
由于您是通过值传递的,go将创建一个临时地址,并且不允许您获取地址。相反,当您递归时,调用valueField.Addr().Interface(),它将传递一个指向嵌入式结构的指针
if valueField.Kind() == reflect.Struct {
- InspectStruct(valueField.Interface())
+ InspectStruct(valueField.Addr().Interface())
}
通过此更改,我得到了您期望的输出:
Field Name: Id, Field Value: 1, Address: 842350527552 , Field type: int , Field kind: int
Field Name: F, Field Value: {2 {3}}, Address: 842350527560 , Field type: lib.V , Field kind: struct
Field Name: Id, Field Value: 2, Address: 842350527560 , Field type: int , Field kind: int
Field Name: F, Field Value: {3}, Address: 842350527568 , Field type: lib.Z , Field kind: struct
Field Name: Id, Field Value: 3, Address: 842350527568 , Field type: int , Field kind: int
感谢您对@OneOfOne答案的补充。非常感谢!
if valueField.Kind() == reflect.Struct {
- InspectStruct(valueField.Interface())
+ InspectStruct(valueField.Addr().Interface())
}
Field Name: Id, Field Value: 1, Address: 842350527552 , Field type: int , Field kind: int
Field Name: F, Field Value: {2 {3}}, Address: 842350527560 , Field type: lib.V , Field kind: struct
Field Name: Id, Field Value: 2, Address: 842350527560 , Field type: int , Field kind: int
Field Name: F, Field Value: {3}, Address: 842350527568 , Field type: lib.Z , Field kind: struct
Field Name: Id, Field Value: 3, Address: 842350527568 , Field type: int , Field kind: int