douliao2493 2014-11-01 11:27
浏览 69
已采纳

通过引用设置接口{}参数

I am having difficulty understanding how to set an interface value that has been passed as a pointer. I am trying to accomplish something along the lines of this:

import "fmt"

var Stuff map[string]interface{}

func main() {
    var num int
    Stuff["key"] = 9001
    get("key", &num)
    fmt.Println("num:", num)
}

func get(k string, v interface{}) {
    *v = Stuff[k]
}

What would I have to do to make my program output be

num: 9001

Edit: is there a possible catch-all solution using reflect?

  • 写回答

5条回答 默认 最新

  • doujing6053 2014-11-09 01:43
    关注

    You can emulate the AppEngine datastore interface using reflect; usually I say minimize reflection, but you (and AppEngine and other ORMs) have no other great option here to present the interface you want. For something emulating Get you:

    • get a reflect.Value with ValueOf()
    • get the type of the thing you want to create
    • create it with reflect.Zero
    • optionally fill in some data with reflect.Field(), etc.
    • use reflect.Indirect() and Value.Set() to set the original through the pointer.

    A trivial example that just zeroes a struct through a pointer is at http://play.golang.org/p/g7dNlrG_vr and copied here:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        i := 1
        clear(&i)
        fmt.Println(i)
    }
    
    func clear(dst interface{}) {
        // ValueOf to enter reflect-land
        dstPtrValue := reflect.ValueOf(dst)
        // need the type to create a value
        dstPtrType := dstPtrValue.Type()
        // *T -> T, crashes if not a ptr
        dstType := dstPtrType.Elem()
        // the *dst in *dst = zero
        dstValue := reflect.Indirect(dstPtrValue)
        // the zero in *dst = zero
        zeroValue := reflect.Zero(dstType)
        // the = in *dst = 0
        dstValue.Set(zeroValue)
    }
    

    For emulating GetMulti you need more steps to work with the slice. An example is at http://play.golang.org/p/G_6jit2t-2 and below:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        s := []int{}
        getMultiZeroes(&s, 10)
        fmt.Println(s)
    }
    
    func getMultiZeroes(slicePtrIface interface{}, howMany int) {
        // enter `reflect`-land
        slicePtrValue := reflect.ValueOf(slicePtrIface)
        // get the type
        slicePtrType := slicePtrValue.Type()
        // navigate from `*[]T` to `T`
        sliceElemType := slicePtrType.Elem().Elem() // crashes if input type not `*[]T`
        // we'll need this to Append() to
        sliceValue := reflect.Indirect(slicePtrValue)
        // and this to Append()
        sliceElemValue := reflect.Zero(sliceElemType)
    
        // append requested number of zeroes
        for i := 0; i < howMany; i++ {
            // s := append(s, v)
            sliceValue.Set(reflect.Append(sliceValue, sliceElemValue))
        }
    }
    

    In live code (as opposed to testing like you're doing), it'd be faster to use a type switch (as Martin suggested) so that specialized native code runs for each type; that might also be handy if you have different behavior by type. An example for GetMulti is at http://play.golang.org/p/q-9WyUqv6P and below:

    package main
    
    import "fmt"
    
    func main() {
        s := []int{}
        getZeroes(&s)
        fmt.Println(s)
    
        fails := []float32{}
        getZeroes(&fails)
    }
    
    func getZeroes(slicePtrIface interface{}) {
        switch sp := slicePtrIface.(type) {
        case *[]int:
            (*sp) = append((*sp), 0, 0)
        case *[]string:
            (*sp) = append((*sp), "", "")
        default:
            panic(fmt.Sprintf("getZeroes: passed type %T, which is not a pointer to a slice of a supported type", slicePtrIface))
        }
    }
    

    You could even trivially combine the two; write custom code for common types and call the slow reflect-based version in the default case. Demo at http://play.golang.org/p/6qw52B7eC3 (not copying because it's a such a simple stitching together of the two above).

    There happened to be another recent question on how to make a value to pass to GetMulti, rather than emulating the GetMulti itself, if that comes up.


    More for general reference than to answer this:

    "Go lacks pass by reference" is useful to know, but also needs some elaboration. Go has pointers, and other types like slices that contain pointers to data. The sense in which there isn't "pass by reference" is just that Go will never change a value argument (int, struct) into a pointer implicitly. C++ reference arguments do exactly that: C++ void f(i int&) { i++; } changes i in the caller without the caller explicitly passing in a pointer at the callsite. func (i int) { i++ } doesn't.

    In Go, you can look at the types passed to a function call and tell what data it can change. With C++ reference arguments or some languages' "pass by reference" semantics, any call might change locals; you can't tell without looking up the declarations.

    For purposes of avoiding unnecessary copying of data, there are already pointers in the implementations of slice, string, map, interface, and channel values. Of those types, pointers, slices, and maps will actually let you modify data through them. Also, like in C++, Go's this-like receiver parameter can be a pointer without an explicit & in the calling code. There's more about this in Russ Cox's godata post and this summary on when you need a pointer or not.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(4条)

报告相同问题?

悬赏问题

  • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
  • ¥15 (标签-MATLAB|关键词-多址)
  • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
  • ¥500 52810做蓝牙接受端
  • ¥15 基于PLC的三轴机械手程序
  • ¥15 多址通信方式的抗噪声性能和系统容量对比
  • ¥15 winform的chart曲线生成时有凸起
  • ¥15 msix packaging tool打包问题
  • ¥15 finalshell节点的搭建代码和那个端口代码教程
  • ¥15 Centos / PETSc / PETGEM