我知道Go中的值都是通过值传递的,这意味着如果我给一个函数赋一个切片并且该函数使用内置append
函数附加到切片,那么原始切片将不具有附加在函数范围内。
例如:
nums := []int{1, 2, 3}
func addToNumbs(nums []int) []int {
nums = append(nums, 4)
fmt.Println(nums) // []int{1, 2, 3, 4}
}
fmt.Println(nums) // []int{1, 2, 3}
这对我来说是一个问题,因为我试图对累积的切片进行递归,基本上是一个reduce
类型的函数,除了reducer调用它自己。
以下是一个例子:
func Validate(obj Validatable) ([]ValidationMessage, error) {
messages := make([]ValidationMessage, 0)
if err := validate(obj, messages); err != nil {
return messages, err
}
return messages, nil
}
func validate(obj Validatable, accumulator []ValidationMessage) error {
// If something is true, recurse
if something {
if err := validate(obj, accumulator); err != nil {
return err
}
}
// Append to the accumulator passed in
accumulator = append(accumulator, message)
return nil
}
上面的代码给出了与第一个示例相同的错误,因为accumulator
没有获得所有附加值,因为它们只存在于函数的范围内。
为了解决这个问题,我将指针结构传递给函数,该结构包含累加器。该解决方案效果很好。
我的问题是,有没有更好的方法来做到这一点,并且我的方法是Go的惯用法吗?
更新解决方案(感谢icza):
我只是在递归函数中返回切片。这样的面孔,应该想到这一点。
func Validate(obj Validatable) ([]ValidationMessage, error) {
messages := make([]ValidationMessage, 0)
return validate(obj, messages)
}
func validate(obj Validatable, messages []ValidationMessage) ([]ValidationMessage, error) {
err := v.Struct(obj)
if _, ok := err.(*validator.InvalidValidationError); ok {
return []ValidationMessage{}, errors.New(err.Error())
}
if _, ok := err.(validator.ValidationErrors); ok {
messageMap := obj.Validate()
for _, err := range err.(validator.ValidationErrors) {
f := err.StructField()
t := err.Tag()
if v, ok := err.Value().(Validatable); ok {
return validate(v, messages)
} else if _, ok := messageMap[f]; ok {
if _, ok := messageMap[f][t]; ok {
messages = append(messages, ValidationMessage(messageMap[f][t]))
}
}
}
}
return messages, nil
}
答案 0 :(得分:24)
如果要将切片作为参数传递给函数,并希望该函数修改原始切片,则必须将指针传递给切片:
func myAppend(list *[]string, value string) {
*list = append(*list, value)
}
我不知道Go编译器是否天真或聪明。性能留作评论部分的练习。
答案 1 :(得分:3)
如果切片的当前大小不足以附加新值从而更改底层数组,则切片会根据需要动态增长。如果未返回此新切片,则您的附加更改将不可见。
示例:
package main
import (
"fmt"
)
func noReturn(a []int, data ...int) {
a = append(a, data...)
}
func returnS(a []int, data ...int) []int {
return append(a, data...)
}
func main() {
a := make([]int, 1)
noReturn(a, 1, 2, 3)
fmt.Println(a) // append changes will not visible since slice size grew on demand changing underlying array
a = returnS(a, 1, 2, 3)
fmt.Println(a) // append changes will be visible here since your are returning the new updated slice
}
结果:
[0]
[0 1 2 3]
注意:
答案 2 :(得分:0)
将数据追加到切片时,如果切片的基础数组没有足够的空间,则会分配一个新数组。然后将旧数组中的元素复制到此新内存中,并在后面添加新数据
答案 3 :(得分:0)
您传递的切片是对数组的引用,这意味着大小是固定的。如果你只是修改了存储的值,那没关系,值会在被调用函数之外更新。
但是如果你在切片中添加了新元素,它会重新切片以容纳新元素,换句话说,将创建一个新切片并且不会覆盖旧切片。
总结一下,如果你需要对切片进行扩展或切割,将指针传递给切片。否则,使用切片本身就足够了。