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,to
reflect.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