将结构 属性 (切片)传递给从中删除元素的函数时的奇怪行为

Strange behaviour when passing a struct property (slice) to a function that removes elements from it

这些天我开始学习 Go,但一直试图将结构 属性 的值(切片)传递给函数。显然它是作为引用传递的(或者它持有指向其切片的指针)并且函数内部所做的更改会影响它。

这是我的代码,其中 testFunction 应该接收一个切片,删除它的前 3 个元素并打印更新后的值,但不会影响外部:

package main

import (
    "fmt"
)

type testStruct struct {
    testArray []float64
}

var test = testStruct {
    testArray: []float64{10,20,30,40,50},
}

func main() {
    fmt.Println(test.testArray)
    testFunction(test.testArray)
    fmt.Println(test.testArray)
}

func testFunction(array []float64) {
    for i:=0; i<3; i++ {
        array = removeFrom(array, 0)
    }
    fmt.Println(array)
}

func removeFrom(array []float64, index int) []float64 {
    return append(array[:index], array[index+1:]...)
}

输出:

[10 20 30 40 50]
[40 50]
[40 50 50 50 50]

我的问题是:是什么导致第三个 fmt.Println 打印出这个奇怪的结果?

游乐场:https://play.golang.org/p/G8W3H085In

p.s.: 这段代码只是一个例子。删除某些东西的第一个元素不是我的目标。我只想知道是什么导致了这种奇怪的行为。

切片是复合类型。它有一个指向数据、长度和容量的指针。当您将它作为参数传递时,您传递的是这些值、指针、长度和容量;它们始终是副本。

在您的情况下,您在调用 removeFrom() 时修改了切片中的数据,您可以这样做,因为您已将指向原始数据的指针的值复制到 func 中,但是长度和容量在该功能范围之外保持不变,因为它们不是指针。

因此,当您从 main() 再次打印它时,您会看到更改后的值,但它仍使用原始长度和容量,因为对其他函数范围内的值所做的任何更改实际上都在这些值。

通常我们不知道给定的追加调用是否会导致重新分配,因此我们不能假设原始切片与结果切片引用相同的数组,也不能假定它引用不同的数组.

要正确使用切片,请务必记住,虽然底层数组的元素是间接的,但切片的指针、长度和容量不是。

因此,通常将 append 调用的结果分配给同一个切片变量:

array = append(array, ...)

所以总而言之,要收到想要的结果,请务必记住将追加函数分配给新的或相同的切片变量。

这是更正后的工作代码:

package main

import (
    "fmt"
)

type testStruct struct {
    testArray []float64
}

var test = testStruct {
    testArray: []float64{10,20,30,40,50},
}

func main() {
    fmt.Println(test.testArray)
    a := testFunction(test.testArray)
    fmt.Println(a)
}

func testFunction(array []float64)[]float64 {
    for i:=0; i<3; i++ {
        array = removeFrom(array, 0)
    }
    fmt.Println(array)
    return array
}

func removeFrom(array []float64, index int) []float64 {
    return append(array[:index], array[index+1:]...)
}

检查 Go Playground.

上的工作代码

另一个解决方案是通过指针引用传递数组参数:

func testFunction(array *[]float64) {
    for i:=0; i<3; i++ {
        *array = removeFrom(*array, 0)
    }
    fmt.Println(*array)
}

Go Playground

这是一篇关于切片 https://blog.golang.org/slices 的有用博客 post。它特别指出了这一点。

It's important to understand that even though a slice contains a pointer, it is itself a value. Under the covers, it is a struct value holding a pointer and a length. It is not a pointer to a struct.

您看到 [40 50 50 50 50] 的原因是因为您更改了切片中的值,但没有更改切片本身(它是 cap 和 len)