golang切片是否超值?

duc*_*uck 32 pass-by-value go slice

在Golang,我正在尝试为我的旅行推销员问题制作一个争夺切片功能.虽然这样做我注意到当我开始编辑切片时,我给每个传递它时的加扰功能是不同的.

经过一些调试后我发现它是由于我编辑了函数内部的切片.但由于Golang应该是一种"通过价值传递"的语言,这怎么可能呢?

https://play.golang.org/p/mMivoH0TuV

我提供了一个游乐场链接来展示我的意思.通过删除第27行,您获得的输出与输入不同,这应该没有区别,因为该函数在作为参数传入时应该创建自己的切片副本.
有人可以解释这种现象吗?

icz*_*cza 82

是的,Go中的所有内容都按值传递.切片也是.但切片值是标题,描述了支持数组的连续部分,切片值仅包含指向实际存储元素的数组的指针.切片值不包括其元素(与数组不同).

因此,当您将切片传递给函数时,将从此标头创建一个副本,包括指针,该指针将指向相同的后备阵列.修改切片的元素意味着修改后备阵列的元素,因此共享相同后备阵列的所有切片将"观察"更改.

要查看切片标头中的内容,请查看reflect.SliceHeader类型:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
Run Code Online (Sandbox Code Playgroud)

请参阅相关/可能重复的问题: Golang函数参数是否作为copy-on-write传递?

阅读博文:Go Slices:用法和内部

  • @Sahas切片头包含长度.如果追加元素,则必须增加长度,因此即使后备数组有空间用于此附加元素并且没有分配新的后备数组并且复制现有元素,原始切片头也不会"看到"它.这就是内置`append()`函数必须返回一个新切片值的原因.更不用说是否必须分配新阵列...... (5认同)
  • @ user4901806如果您不想修改传递的切片的元素(它指向的支持数组的元素),那么是,复制. (2认同)
  • 它可能会丰富答案以澄清“一切按值传递”。切片和映射都是按值传递,但称为“引用类型”(https://blog.golang.org/maps)。更令人困惑的是,它们的引用类型行为存在差异。例如,将一个切片从 func A 传递到 func B,向切片添加一个值,然后 func A 将看不到添加的值。但是将一个映射从 func A 传递到 func B,添加到 func B 中的映射,然后 func A *将*看到添加的值。去玩 https://play.golang.org/p/o9gm7JtDbMm (2认同)

小智 11

您可以在下面找到一个示例。简而言之,切片也是按值传递的,但原始切片和复制的切片链接到相同的底层数组。如果此切片之一发生更改,则底层数组会发生更改,然后其他切片也会发生更改。

package main

import "fmt"

func main() {
    x := []int{1, 10, 100, 1000}
    double(x)
    fmt.Println(x) // ----> 3 will print [2, 20, 200, 2000] (original slice changed)
}

func double(y []int) {
    fmt.Println(y) // ----> 1 will print [1, 10, 100, 1000]
    for i := 0; i < len(y); i++ {
        y[i] *= 2
    }
    fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}
Run Code Online (Sandbox Code Playgroud)


小智 7

Slice 可以通过值传递给函数,但是我们不应该使用append 来向函数中的slice 添加值,而应该直接使用赋值。原因是追加将创建新的内存并将值复制到其中。这是一个例子。

去游乐场

     // Go program to illustrate how to
        // pass a slice to the function
        package main
        
        import "fmt"
        
        // Function in which slice
        // is passed by value
        func myfun(element []string) {
        
            // Here we only modify the slice
            // Using append function
            // Here, this function only modifies
            // the copy of the slice present in
            // the function not the original slice
            element = append(element, "blackhole")
            fmt.Println("Modified slice: ", element)
        }
        
        func main() {
        
            // Creating a slice
            slc := []string{"rocket", "galaxy", "stars", "milkyway"}
            fmt.Println("Initial slice: ", slc)
            //slice pass by value
            myfun(slc)
            fmt.Println("Final slice: ", slc)
        }
Output-
    Initial slice:  [rocket galaxy stars milkyway]
    Modified slice:  [rocket galaxy stars milkyway blackhole]
    Final slice:  [rocket galaxy stars milkyway]
Run Code Online (Sandbox Code Playgroud)

去游乐场

    // Go program to illustrate how to
        // pass a slice to the function
        package main
        import "fmt"
        
        // Function in which slice
        // is passed by value
        func myfun(element []string) {
        
            // Here we only modify the slice
            // Using append function
            // Here, this function only modifies
            // the copy of the slice present in
            // the function not the original slice
            element[0] = "Spaceship"
            element[4] = "blackhole"
            element[5] = "cosmos"
            fmt.Println("Modified slice: ", element)
        }
        
        func main() {
        
            // Creating a slice
            slc := []string{"rocket", "galaxy", "stars", "milkyway", "", ""}
            fmt.Println("Initial slice: ", slc)
            //slice pass by value
            myfun(slc)
            fmt.Println("Final slice: ", slc)
        }
Output-
    Initial slice:  [rocket galaxy stars milkyway  ]
    Modified slice:  [Spaceship galaxy stars milkyway blackhole cosmos]
    Final slice:  [Spaceship galaxy stars milkyway blackhole cosmos]
Run Code Online (Sandbox Code Playgroud)


Has*_*sef 5

当它传递时,切片与指向底层数组的指针一起传递,因此切片一个指向底层数组的小结构。小结构被复制,但它仍然指向相同的底层数组。包含切片元素的内存块通过“引用”传递。保存容量、元素数量和指向元素的指针的切片信息三元组通过值传递。

\n\n

处理传递给函数的切片的最佳方法(如果切片的元素被操作到函数中,并且我们不希望这反映在元素内存块上)是使用copy(s, *c)以下方式复制它们:

\n\n
package main\n\nimport "fmt"\n\ntype Team []Person\ntype Person struct {\n    Name string\n    Age  int\n}\n\nfunc main() {\n    team := Team{\n        Person{"Hasan", 34}, Person{"Karam", 32},\n    }\n    fmt.Printf("original before clonning: %v\\n", team)\n    team_cloned := team.Clone()\n    fmt.Printf("original after clonning: %v\\n", team)\n    fmt.Printf("clones slice: %v\\n", team_cloned)\n}\n\nfunc (c *Team) Clone() Team {\n    var s = make(Team, len(*c))\n    copy(s, *c)\n    for index, _ := range s {\n        s[index].Name = "change name"\n    }\n    return s\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

但要小心,如果该切片包含sub slice进一步的复制,则需要进行进一步的复制,因为我们仍将共享指向相同内存块元素的子切片元素,示例如下:

\n\n
package main\n\nimport "fmt"\n\ntype Team []Person\ntype Person struct {\n    Name string\n    Age  int\n}\n\nfunc main() {\n    team := Team{\n        Person{"Hasan", 34}, Person{"Karam", 32},\n    }\n    fmt.Printf("original before clonning: %v\\n", team)\n    team_cloned := team.Clone()\n    fmt.Printf("original after clonning: %v\\n", team)\n    fmt.Printf("clones slice: %v\\n", team_cloned)\n}\n\nfunc (c *Team) Clone() Team {\n    var s = make(Team, len(*c))\n    copy(s, *c)\n    for index, _ := range s {\n        s[index].Name = "change name"\n    }\n    return s\n}\n
Run Code Online (Sandbox Code Playgroud)\n