使用反射获取指向值的指针

时间:2014-06-22 04:39:29

标签: 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{}) {
     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())
        }
    }
}

这是结构实例化/初始化后的实际测试:

t := new(T)
t.Id = 1
t.F = *new(V)
t.F.Id = 2
t.F.F = *new(Z)
t.F.F.Id = 3

InspectStruct(t)

最后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。 我的问题是,尽管已经为整个结构初始化了所有字段,但是#34; t&#34;层次结构,我无法获得位于比#34; t&#34;更高深度的任何字段的地址。我真的很感激任何帮助。

4 个答案:

答案 0 :(得分:12)

传递reflect.Value代替interface{}似乎可以解决问题,但我不知道为什么valueField.Interface()无效。

工作示例:http://play.golang.org/p/nleA2YWMj8

func InspectStructV(val reflect.Value) {
    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.Sprintf("0x%X", 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 {
            InspectStructV(valueField)
        }
    }
}

func InspectStruct(v interface{}) {
    InspectStructV(reflect.ValueOf(v))
}

答案 1 :(得分:11)

Interface()不起作用的原因是它返回的接口包装器。为了了解正在发生的事情,让我们看看我们在没有反思的情况下做了什么:

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!
}

Playground

但是,请考虑interface{}这会返回。什么是包装? ptr.F - 即副本。这是value.Interface的作用,它会返回包裹字段的interface{}。不再有任何指针元数据,它与原始结构完全分离。

正如您所注意到的,passing a value directlyreflect.ValueOf将始终为CanAddr返回false作为“顶级” - 因为该地址毫无意义,因为它会为您提供价值副本的地址,改变它并不意味着什么。 (请记住,指针也是值 - 如果你想要像*Foo这样的指针值字段的地址,你真的在​​寻找**Foo)。

所以,在我们上面的示例中,如果我们天真地传入reflect.ValueOf(ExtractField(ms)),我们会得到ValueOf f,这不仅没有您想要的地址 - - 根据反映它甚至不可寻址,因为就反射而言,从不给出有效地址(它可以给你的唯一地址是{内部值副本的地址) {1}} struct)。

那么为什么将Value传递到兔子洞下面呢?好吧,唯一真实的说法是Value在您使用reflect.ValueElem时维护必要的元数据,而Field则不能。因此,虽然interface{}可能如下所示:

reflect.Value

它可以给你的是这个

// Disclaimer: not the real structure of a reflect.Value
type Value struct {
    fieldAddress uintptr
    value        Foo
}

答案 2 :(得分:2)

我今天去了一个反射兔子洞,从学习这段代码和Jsor的答案中学到了很多,谢谢。我得出了一个不同的结论,虽然你的问题导致了一个更直接的解决方案:

1)当你最初调用函数时,你正在传入指向结构的指针,但是......

2)当您通过调用InspectStruct(valueField.Interface())&#39;进行递归时,不是通过指针传递嵌入式结构,而是按值传递它。

由于您通过了值,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

答案 3 :(得分:1)

@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())
    }

需要它,因为有时您可以从零值结构中请求接口。它会发生,它会恐慌。