如何在 Golang 中通过引用传递具有值类型接口的 map 切片

How to pass by reference a slice of maps with value type interface in Golang

我想通过引用函数来传递 []map[string]interface{}。这是我的尝试。

package main

import "fmt"

func main() {

    b := map[string]int{"foo": 1, "bar": 2}
    a := [...]map[string]int{b}

    fmt.Println(a)

    editit(a)

    fmt.Println(a)
}

func editit(a interface{}) {
    fmt.Println(a)

    b := map[string]int{"foo": 3, "bar": 4}
    a = [...]map[string]int{b}

    fmt.Println(a)
}

https://play.golang.org/p/9Bt15mvud1

这是另一个尝试,也是我最终想做的。这不会编译。

func (self BucketStats) GetSamples() {

    buckets := []make(map[string]interface{})
    self.GetAuthRequest(self.url, &buckets)

    //ProcessBuckets()
}

func (self BucketStats) GetAuthRequest(rurl string, **data interface{}) (err error) {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return
    }

    // it's all for this!!!
    err = json.Unmarshal(body, data)

    return
}

该函数正在将 **interface{} 传递给 Unmarshal。要将 *[]map[string]interface{} 传递给 Unmarshal,请将函数签名更改为:

 func (self BucketStats) GetAuthRequest(rurl string, data interface{}) (err error) {

这里有几处错误。

首先,[...]map[string]int{b}其实并不是一个切片,而是一个定长数组。 [...] 语法表示 "make an array, and set the length at compile time based on what's being put into it"。切片语法很简单 []map[string]int{b}。因此,您对 editit(a) 的调用实际上传递的是数组的副本,而不是引用(切片是天生的引用,数组不是)。当 aeditit() 中重新分配时,您正在重新分配副本,而不是原始文件,因此没有任何变化。

其次,使用接口指针几乎没有用。事实上,Go 运行时在几个版本中被改回不自动取消对接口指针的引用(就像它对几乎所有其他东西的指针所做的那样)以阻止这种习惯。接口已经是天生的引用,所以没有理由指向一个接口。

第三,您实际上不需要在此处传递对接口的引用。您正在尝试解组为该接口中包含的基本数据结构。您实际上并不关心界面本身。 GetAuthRequest(rurl string, data interface{}) 在这里工作得很好。

func (self BucketStats) GetSamples() {

    var buckets []map[string]interface{}
    self.GetAuthRequest(self.url, &buckets)

    //ProcessBuckets()
}

func (self BucketStats) GetAuthRequest(rurl string, data interface{}) (err error) {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return
    }

    // it's all for this!!!
    err = json.Unmarshal(body, data)

    return
}

让我按顺序向您介绍到底发生了什么:

  • buckets := var buckets []map[string]interface{} 我们这里不需要 make,因为 json.Unmarshal() 会为我们填充它。

  • self.GetAuthRequest(self.url, &buckets) 这会将引用传递到接口字段中。在 GetAuthRequest 中,data 是一个底层类型为 *[]map[string]interface{} 的接口,其底层值等于​​ GetSamples().[=48 中原始 buckets 变量的地址=]

  • err = json.Unmarshal(body, data) 这会将接口 data 按值传递给 json.Unmarshal() 的接口参数。在 json.Unmarshal() 内部,它有新的接口 v,基础类型为 *[]map[string]interface{},基础值等于 GetSamples() 中原始 buckets 变量的地址。这个接口是一个不同的变量,在内存中具有不同的地址,与在 GetAuthRequest 中保存相同数据的接口不同,但是数据被复制过来,并且数据包含对你的切片的引用,所以你是还是不错的

  • json.Unmarshal()会通过反射,用你请求中的数据填充你交给它的接口指向的切片。它有一个对你交给它的切片头 buckets 的引用,即使它通过两个接口到达那里,所以它所做的任何更改都会影响原始 buckets 变量。

当您一路回到 ProcessBuckets() 时,buckets 变量将包含您的所有数据。


作为辅助建议,如果您的函数超过几行,请不要使用命名 returns。最好显式 return 你的变量。由于可变阴影,这一点尤为重要。例如,在您的 GetAuthRequest() 函数中,它永远不会 return 非零错误。这是因为您在函数签名中声明了一个错误变量 err,但您立即使用 req, err := http.NewRequest("GET", rurl, nil) 中的简短声明用局部变量 err 隐藏了它。对于函数的其余部分,err 现在指的是这个新的错误变量,而不是定义为 return 变量的那个。结果,当你 return 时,return 中的原始 err 变量是 always nil。更好的风格是:

func (self BucketStats) GetAuthRequest(rurl string, **data interface{}) error {
    client := &http.Client{}

    req, err := http.NewRequest("GET", rurl, nil)
    req.SetBasicAuth(self.un, self.pw)
    resp, err := client.Do(req)
    if err != nil {
            return err
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
            return err
    }

    // it's all for this!!!
    return json.Unmarshal(body, data)
}