如何在 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)
的调用实际上传递的是数组的副本,而不是引用(切片是天生的引用,数组不是)。当 a
在 editit()
中重新分配时,您正在重新分配副本,而不是原始文件,因此没有任何变化。
其次,使用接口指针几乎没有用。事实上,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)
}
我想通过引用函数来传递 []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)
的调用实际上传递的是数组的副本,而不是引用(切片是天生的引用,数组不是)。当 a
在 editit()
中重新分配时,您正在重新分配副本,而不是原始文件,因此没有任何变化。
其次,使用接口指针几乎没有用。事实上,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)
}