切片是通过值传递的吗?

在围棋中,我试图为我的旅行推销员的问题作出一个扰乱切片函数。在这样做的时候,我注意到当我开始编辑切片时,我给出的加密函数每次传入时都不一样。

经过一些调试,我发现这是由于我编辑内部的切片函数。但是,既然 Go 应该是一种“按值传递”的语言,这怎么可能呢?

Https://play.golang.org/p/mmivoh0tuv

我已经提供了一个操场链接,以显示我的意思。 通过删除第27行,您将获得与保留该行不同的输出,这应该不会产生什么影响,因为当作为参数传入时,函数应该创建自己的切片副本。
有人能解释一下这个现象吗?

73314 次浏览

Everything in Go is passed by value, slices too. But a slice value is a header, describing a contiguous section of a backing array, and a slice value only contains a pointer to the array where the elements are actually stored. The slice value does not include its elements (unlike arrays).

So when you pass a slice to a function, a copy will be made from this header, including the pointer, which will point to the same backing array. Modifying the elements of the slice implies modifying the elements of the backing array, and so all slices which share the same backing array will "observe" the change.

To see what's in a slice header, check out the reflect.SliceHeader type:

type SliceHeader struct {
Data uintptr
Len  int
Cap  int
}

See related / possible duplicate question: Performance of function slice parameter vs global variable?

Read blog post: Go Slices: usage and internals

Please note that when you pass a slice to a function, if the function modifies the "existing" elements of the slice, the caller will see / observe the changes. If the function adds new elements to the slice, that requires changing the slice header (the length at a minimum, but may also involve allocating a new backing array), which the caller will not see (not without returning the new slice header).

Not with maps, because maps are pointers under the hood, and if you pass a map to a function and the function adds a new entry to the map, the map pointer will not change so the caller will see the changed map (the new entry) without returning the map after change.

Also regarding slices and maps, see Map initialization in Go and why slice values can sometimes go stale but never map values?

Slices when its passed it’s passed with the pointer to underlying array, so a slice is a small structure that points to an underlying array. The small structure is copied, but it still points to the same underlying array. the memory block containing the slice elements is passed by "reference". The slice information triplet holding the capacity, the number of element and the pointer to the elements is passed by value.

The best way to handle slices passing to function (if the elements of the slice are manipulated into the function, and we do not want this to be reflected at the elements memory block is to copy them using copy(s, *c) as:

package main


import "fmt"


type Team []Person
type Person struct {
Name string
Age  int
}


func main() {
team := Team{
Person{"Hasan", 34}, Person{"Karam", 32},
}
fmt.Printf("original before clonning: %v\n", team)
team_cloned := team.Clone()
fmt.Printf("original after clonning: %v\n", team)
fmt.Printf("clones slice: %v\n", team_cloned)
}


func (c *Team) Clone() Team {
var s = make(Team, len(*c))
copy(s, *c)
for index, _ := range s {
s[index].Name = "change name"
}
return s
}

But be careful, if this slice is containing a sub slice further copying is required, as we'll still have the sub slice elements sharing pointing to the same memory block elements, an example is:

type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
Warehouse string
Item      string
Batches   Lots
}
type Lots []Lot
type Lot struct {
Date  time.Time
Key   string
Value float64
}


func main() {
ins := Inventory{
Warehouse: "DMM",
Item:      "Gloves",
Batches: Lots{
Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
},
}


inv2 := CloneFrom(c Inventories)
}


func (i *Inventories) CloneFrom(c Inventories) {
inv := new(Inventories)
for _, v := range c {
batches := Lots{}
for _, b := range v.Batches {
batches = append(batches, Lot{
Date:  b.Date,
Key:   b.Key,
Value: b.Value,
})
}


*inv = append(*inv, Inventory{
Warehouse: v.Warehouse,
Item:      v.Item,
Batches:   batches,
})
}
(*i).ReplaceBy(inv)
}


func (i *Inventories) ReplaceBy(x *Inventories) {
*i = *x
}

You can find an example below. Briefly slices is also passed by value but original slice and copied slice are linked to the same underlying array. If one of this slice changes, then underlying array changes, then other slice changes.

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)
}

To complement this post, here is an example of passing by reference for the Golang PlayGround you shared:

type point struct {
x int
y int
}


func main() {
data := []point\{\{1, 2}, {3, 4}, {5, 6}, {7, 8}}
makeRandomDatas(&data)
}


func makeRandomDatas(dataPoints *[]point) {
for i := 0; i < 10; i++ {
if len(*dataPoints) > 0 {
fmt.Println(makeRandomData(dataPoints))
} else {
fmt.Println("no more elements")
}
}


}


func makeRandomData(cities *[]point) []point {
solution := []point{(*cities)[0]}                 //create a new slice with the first item from the old slice
*cities = append((*cities)[:0], (*cities)[1:]...) //remove the first item from the old slice
return solution


}

Slice will work with pass by value to the function, But we should not use append to add values to slice in the function, instead we should use the assignment directly. Reason being that append will create new memory and copy values to that. Here is the example.

Go playground

     // 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]

Go Playground

    // 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]