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

Go 将切片作为函数参数传递,并修改原始切片,go,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,

我知道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
}
上面的代码给出了与第一个示例相同的错误,
累加器
没有获得所有附加值,因为它们只存在于函数的范围内

为了解决这个问题,我将指针结构传递到函数中,该结构包含累加器。这个解决方案很有效

我的问题是,有没有更好的方法来做到这一点,我的方法是惯用的吗

更新的解决方案(感谢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
}

如果切片的当前大小不足以追加新值从而更改基础数组,则切片将根据需要动态增长。如果未返回此新切片,您的追加更改将不可见

例如:

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]
注:


  • 如果在不向切片添加新项目的情况下更新切片中的项目,则不必返回切片

  • 将数据追加到切片时,如果切片的底层数组没有足够的空间,将分配一个新数组。然后,旧数组中的元素将被复制到这个新内存中,同时在后面添加新数据。如果要将一个切片作为参数传递给函数,并让该函数修改原始切片,则必须传递指向该切片的指针:

    func myAppend(list *[]string, value string) {
        *list = append(*list, value)
    }
    

    我不知道围棋编译器在这方面是幼稚还是聪明;性能留作注释部分的练习。

    您传递的切片是对数组的引用,这意味着大小是固定的。如果您只是修改了存储的值,那么该值将在调用的函数外部更新

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


    作为总结,如果需要扩展或剪切切片,请将指针传递给切片。否则,使用切片本身就足够了。

    返回新切片,就像内置的
    append()
    那样(并在调用方重新分配返回值),或传递指向切片的指针,不是切片头。我不能返回新的切片,因为我必须处理该切片以进行递归,但我认为将指针传递给slight可能会有效吗?让我试试..递归调用也可以返回值,我看不出有什么问题。如果您使用传递切片指针,请注意,处理它们更“冗长”,请参见示例。您知道吗,您完全正确。不知道我在想什么。从递归函数返回切片对我来说效果很好。非常感谢。@OmarIlias通过一个切片头足够快,因为它很小。如果您将指针传递给它,传递可能会更快,但为了使用它,您必须“不断”(每次使用时)取消对它的引用,代码将更难看,因此您将失去优势。试想一下:内置的
    append()
    可以编写为获取一个指针,它不需要返回新的切片(也不需要重新分配结果),但它在另一条路径上需要切片而不是指向它的指针。谢谢,这是一个很好的解释,然而,它并没有提供问题的解决方案:)尽管如此,我已经通过在递归函数中返回切片找到了解决方案,然后我不必尝试操作指向切片的指针或传递包含切片的指针结构。“如果切片已初始化,大小足以附加新值,则不必返回切片。“这是错误的。如果追加到切片,即使基础数组有足够的空间容纳追加的值,追加的值也不会出现在调用函数的切片中,因为切片标头还包含长度和容量字段,并且追加不会更新这些字段。您可以通过超出切片长度的子切片来访问它们,但它们不会自动显示。示例:实际上,它显然比作为全局变量访问数组要快——这解释了为什么追加操作并不总是修改原始传递的切片,即使切片是“引用类型”,因为底层数组的头是指针+1作为解释,尽管在实施/示例方面,本可以使用此评论做得更好