将切片作为函数参数传递,并修改原始切片

时间:2018-03-22 12:28:38

标签: go

我知道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
}

4 个答案:

答案 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]

注意:

  1. 如果要更新切片中的项目而不添加要切片的新项目
  2. ,则不必返回切片

答案 2 :(得分:0)

将数据追加到切片时,如果切片的基础数组没有足够的空间,则会分配一个新数组。然后将旧数组中的元素复制到此新内存中,并在后面添加新数据

答案 3 :(得分:0)

您传递的切片是对数组的引用,这意味着大小是固定的。如果你只是修改了存储的值,那没关系,值会在被调用函数之外更新。

但是如果你在切片中添加了新元素,它会重新切片以容纳新元素,换句话说,将创建一个新切片并且不会覆盖旧切片。

总结一下,如果你需要对切片进行扩展或切割,将指针传递给切片。否则,使用切片本身就足够了。